From e46af576fc35ba848820936d2bafade579441d68 Mon Sep 17 00:00:00 2001 From: Jeremy Stashewsky Date: Sat, 14 May 2022 01:25:05 -0700 Subject: [PATCH 01/28] Preserve dotted timing on subseqent loops (#1231) --- applications/music_player/music_player_worker.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/applications/music_player/music_player_worker.c b/applications/music_player/music_player_worker.c index 7a5847c9..f43aa7be 100644 --- a/applications/music_player/music_player_worker.c +++ b/applications/music_player/music_player_worker.c @@ -59,9 +59,10 @@ static int32_t music_player_worker_thread_callback(void* context) { float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4); float duration = 60.0 * osKernelGetTickFreq() * 4 / instance->bpm / note_block->duration; - while(note_block->dots > 0) { + uint32_t dots = note_block->dots; + while(dots > 0) { duration += duration / 2; - note_block->dots--; + dots--; } uint32_t next_tick = furi_hal_get_tick() + duration; float volume = instance->volume; From f6384116a1e501d2925013f6fae7e1d7cb6786cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 16 May 2022 22:01:57 +0300 Subject: [PATCH 02/28] Github: update runner config (#1236) --- .github/workflows/build.yml | 4 ++-- .github/workflows/lint_c.yml | 2 +- .github/workflows/reindex.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1a0a91e6..d37622f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ env: jobs: main: - runs-on: [self-hosted,Office] + runs-on: [self-hosted,FlipperZero] steps: - name: 'Cleanup workspace' uses: AutoModality/action-clean@v1 @@ -166,7 +166,7 @@ jobs: compact: if: ${{ !startsWith(github.ref, 'refs/tags') }} - runs-on: [self-hosted,koteeq] + runs-on: [self-hosted,FlipperZero] steps: - name: 'Cleanup workspace' uses: AutoModality/action-clean@v1 diff --git a/.github/workflows/lint_c.yml b/.github/workflows/lint_c.yml index 52a5e9d7..97ef9fb9 100644 --- a/.github/workflows/lint_c.yml +++ b/.github/workflows/lint_c.yml @@ -14,7 +14,7 @@ env: jobs: lint_c_cpp: - runs-on: [self-hosted,Office] + runs-on: [self-hosted,FlipperZero] steps: - name: 'Cleanup workspace' uses: AutoModality/action-clean@v1 diff --git a/.github/workflows/reindex.yml b/.github/workflows/reindex.yml index 49c47eec..78f9cbea 100644 --- a/.github/workflows/reindex.yml +++ b/.github/workflows/reindex.yml @@ -7,7 +7,7 @@ on: jobs: reindex: name: 'Reindex updates' - runs-on: [self-hosted,Office] + runs-on: [self-hosted,FlipperZero] steps: - name: Trigger reindex uses: wei/curl@master From d38dba4a26ee24cddf57acf285587221230e10ad Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 19 May 2022 00:38:06 +0400 Subject: [PATCH 03/28] SubGhz: refactoring frequency analyzer and MegaCode display changes (#1221) * SubGhz: MegaCode display changes * SubGhz: refactoring frequency analyzer * SubGhz: use one stage detection in frequency analyzer, tune bw, datarate and etc * SubGhz: tune analyzer threshold * SubGhz: raise frequency analyzer threshold and rssi sampling config * SubGhz: fix frequency analyzer, small step frequency analysis * SubGhz: subghz_frequency_analyzer_worker * SubGhz: fix SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD * SubGhz: debug logging in frequency analyzer, increase MAGN_TARGET to max value * SubGhz: reduce RSSI delay in frequency scanner * SubGhz: fix delays, remove trace logging from frequency analyzer * SubGhz: cleanup variable names and add comments Co-authored-by: Aleksandr Kutuzov --- .../subghz_frequency_analyzer_worker.c | 153 +++++++++++++----- lib/subghz/protocols/megacode.c | 11 +- 2 files changed, 121 insertions(+), 43 deletions(-) diff --git a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c index c0e1fdba..88e2a621 100644 --- a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -1,24 +1,22 @@ #include "subghz_frequency_analyzer_worker.h" -#include +#include #include #include "../subghz_i.h" +#define TAG "SubghzFrequencyAnalyzerWorker" + +#define SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD -95.0f + static const uint8_t subghz_preset_ook_58khz[][2] = { - {CC1101_FIFOTHR, 0x47}, // The only important bit is ADC_RETENTION, FIFO Tx=33 Rx=32 - {CC1101_MDMCFG4, 0xF5}, // Rx BW filter is 58.035714kHz - {CC1101_TEST2, 0x81}, // FIFOTHR ADC_RETENTION=1 matched value - {CC1101_TEST1, 0x35}, // FIFOTHR ADC_RETENTION=1 matched value + {CC1101_MDMCFG4, 0b11110111}, // Rx BW filter is 58.035714kHz /* End */ {0, 0}, }; static const uint8_t subghz_preset_ook_650khz[][2] = { - {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION - {CC1101_MDMCFG4, 0x17}, // Rx BW filter is 650.000kHz - {CC1101_TEST2, 0x88}, - {CC1101_TEST1, 0x31}, + {CC1101_MDMCFG4, 0b00010111}, // Rx BW filter is 650.000kHz /* End */ {0, 0}, }; @@ -27,7 +25,7 @@ struct SubGhzFrequencyAnalyzerWorker { FuriThread* thread; volatile bool worker_running; - uint8_t count_repet; + uint8_t sample_hold_counter; FrequencyRSSI frequency_rssi_buf; SubGhzSetting* setting; @@ -37,6 +35,16 @@ struct SubGhzFrequencyAnalyzerWorker { void* context; }; +static void subghz_frequency_analyzer_worker_load_registers(const uint8_t data[][2]) { + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + size_t i = 0; + while(data[i][0]) { + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, data[i][0], data[i][1]); + i++; + } + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); +} + // running average with adaptive coefficient static uint32_t subghz_frequency_analyzer_worker_expRunningAverageAdaptive( SubGhzFrequencyAnalyzerWorker* instance, @@ -62,32 +70,75 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { SubGhzFrequencyAnalyzerWorker* instance = context; FrequencyRSSI frequency_rssi = {.frequency = 0, .rssi = 0}; - float rssi; - uint32_t frequency; - uint32_t frequency_start; + float rssi = 0; + uint32_t frequency = 0; + CC1101Status status; //Start CC1101 furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); - furi_hal_subghz_set_frequency(433920000); - furi_hal_subghz_flush_rx(); + + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_flush_rx(&furi_hal_spi_bus_handle_subghz); + cc1101_flush_tx(&furi_hal_spi_bus_handle_subghz); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_MDMCFG3, + 0b11111111); // symbol rate + cc1101_write_reg( + &furi_hal_spi_bus_handle_subghz, + CC1101_AGCCTRL2, + 0b00000111); // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAGN_TARGET 42 dB + cc1101_write_reg( + &furi_hal_spi_bus_handle_subghz, + CC1101_AGCCTRL1, + 0b00001000); // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 1000 - Absolute carrier sense threshold disabled + cc1101_write_reg( + &furi_hal_spi_bus_handle_subghz, + CC1101_AGCCTRL0, + 0b00110000); // 00 - No hysteresis, medium asymmetric dead zone, medium gain ; 11 - 64 samples agc; 00 - Normal AGC, 00 - 4dB boundary + + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); - furi_hal_subghz_rx(); while(instance->worker_running) { osDelay(10); + + float rssi_min = 26.0f; + float rssi_avg = 0; + size_t rssi_avg_samples = 0; + frequency_rssi.rssi = -127.0f; furi_hal_subghz_idle(); - furi_hal_subghz_load_registers(subghz_preset_ook_650khz); + subghz_frequency_analyzer_worker_load_registers(subghz_preset_ook_650khz); + + // First stage: coarse scan for(size_t i = 0; i < subghz_setting_get_frequency_count(instance->setting); i++) { if(furi_hal_subghz_is_frequency_valid( subghz_setting_get_frequency(instance->setting, i))) { - furi_hal_subghz_idle(); - frequency = furi_hal_subghz_set_frequency( + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); + frequency = cc1101_set_frequency( + &furi_hal_spi_bus_handle_subghz, subghz_setting_get_frequency(instance->setting, i)); - furi_hal_subghz_rx(); - osDelay(3); + + cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); + do { + status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); + } while(status.STATE != CC1101StateIDLE); + + cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + + // delay will be in range between 1 and 2ms + osDelay(2); + rssi = furi_hal_subghz_get_rssi(); + + rssi_avg += rssi; + rssi_avg_samples++; + + if(rssi < rssi_min) rssi_min = rssi; + if(frequency_rssi.rssi < rssi) { frequency_rssi.rssi = rssi; frequency_rssi.frequency = frequency; @@ -95,19 +146,41 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { } } - if(frequency_rssi.rssi > -90.0) { - // -0.5 ... 433.92 ... +0.5 - frequency_start = frequency_rssi.frequency - 250000; - //step 10KHz + FURI_LOG_T( + TAG, + "RSSI: avg %f, max %f at %u, min %f", + (double)(rssi_avg / rssi_avg_samples), + (double)frequency_rssi.rssi, + frequency_rssi.frequency, + (double)rssi_min); + + // Second stage: fine scan + if(frequency_rssi.rssi > SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) { + FURI_LOG_D(TAG, "~:%u:%f", frequency_rssi.frequency, (double)frequency_rssi.rssi); + frequency_rssi.rssi = -127.0; furi_hal_subghz_idle(); - furi_hal_subghz_load_registers(subghz_preset_ook_58khz); - for(uint32_t i = frequency_start; i < frequency_start + 500000; i += 10000) { + subghz_frequency_analyzer_worker_load_registers(subghz_preset_ook_58khz); + //-0.3 ... 433.92 ... +0.3 step 10KHz + for(uint32_t i = frequency_rssi.frequency - 300000; + i < frequency_rssi.frequency + 300000; + i += 20000) { if(furi_hal_subghz_is_frequency_valid(i)) { - furi_hal_subghz_idle(); - frequency = furi_hal_subghz_set_frequency(i); - furi_hal_subghz_rx(); - osDelay(3); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); + frequency = cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, i); + + cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); + do { + status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); + } while(status.STATE != CC1101StateIDLE); + + cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + + // delay will be in range between 1 and 2ms + osDelay(2); + rssi = furi_hal_subghz_get_rssi(); if(frequency_rssi.rssi < rssi) { frequency_rssi.rssi = rssi; @@ -117,20 +190,24 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { } } - if(frequency_rssi.rssi > -90.0) { - instance->count_repet = 20; + // Deliver results + if(frequency_rssi.rssi > SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) { + FURI_LOG_D(TAG, "=:%u:%f", frequency_rssi.frequency, (double)frequency_rssi.rssi); + + instance->sample_hold_counter = 20; if(instance->filVal) { frequency_rssi.frequency = subghz_frequency_analyzer_worker_expRunningAverageAdaptive( instance, frequency_rssi.frequency); } - if(instance->pair_callback) + // Deliver callback + if(instance->pair_callback) { instance->pair_callback( instance->context, frequency_rssi.frequency, frequency_rssi.rssi); - + } } else { - if(instance->count_repet > 0) { - instance->count_repet--; + if(instance->sample_hold_counter > 0) { + instance->sample_hold_counter--; } else { instance->filVal = 0; if(instance->pair_callback) instance->pair_callback(instance->context, 0, 0); diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index 48a6908e..6f6240a6 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -401,13 +401,14 @@ void subghz_protocol_decoder_megacode_get_string(void* context, string_t output) string_cat_printf( output, "%s %dbit\r\n" - "Key:%06lX\r\n" - "Sn:%04lX Btn:%X\r\n" - "Facility:%X\r\n", + "Key:0x%06lX\r\n" + "Sn:0x%04lX - %d\r\n" + "Facility:%X Btn:%X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)instance->generic.data, instance->generic.serial, - instance->generic.btn, - instance->generic.cnt); + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); } From 513d098051d7e543ab0648347b22cd5142ffdc84 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Thu, 19 May 2022 14:24:58 +0300 Subject: [PATCH 04/28] Fix cli session open on pin unlock (#1245) --- applications/desktop/helpers/pin_lock.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/applications/desktop/helpers/pin_lock.c b/applications/desktop/helpers/pin_lock.c index 00ac4177..d63398d9 100644 --- a/applications/desktop/helpers/pin_lock.c +++ b/applications/desktop/helpers/pin_lock.c @@ -100,11 +100,12 @@ void desktop_pin_lock_init(DesktopSettings* settings) { } else { furi_hal_rtc_set_pin_fails(0); furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); - furi_hal_usb_enable(); } if(desktop_pin_lock_is_locked()) { - furi_hal_usb_disable(); + Cli* cli = furi_record_open("cli"); + cli_session_close(cli); + furi_record_close("cli"); } } From 9c3f465afdd40fcd576bc4372fef17c5865625b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 19 May 2022 19:07:45 +0300 Subject: [PATCH 05/28] [FL-2537, FL-2539, FL-2534, FL-2534, FL-2525]: UI and UX improvements (#1246) * FL-2537: ON/OFF buttons * FL-2539: cancelable lowbatt shutdown * FL-2534: update storage format results screen, cleanup dialog_ex usage. * FL-2534: storage setting UX rework * FL-2525: unify arrows icons * Remove unused icons * UI: Rename Ok to OK --- applications/bt/bt_service/bt.c | 2 +- .../desktop/views/desktop_view_locked.c | 4 +- applications/gpio/scenes/gpio_scene_start.c | 4 +- .../scenes/ibutton_scene_read_crc_error.c | 6 +- .../scenes/ibutton_scene_read_not_key_error.c | 6 +- .../scenes/ibutton_scene_read_success.c | 7 +-- .../infrared/view/infrared_progress_view.c | 2 +- applications/power/power_service/power.c | 20 ++++++- .../power/power_service/views/power_off.c | 56 ++++++++++++++++-- .../power/power_service/views/power_off.h | 9 +++ .../scenes/storage_settings_scene_benchmark.c | 8 +-- .../storage_settings_scene_factory_reset.c | 13 ++-- .../storage_settings_scene_format_confirm.c | 8 +-- .../storage_settings_scene_formatting.c | 24 +++----- .../storage_settings_scene_internal_info.c | 9 +-- .../scenes/storage_settings_scene_sd_info.c | 9 +-- ... storage_settings_scene_unmount_confirm.c} | 17 +++--- ...d.c => storage_settings_scene_unmounted.c} | 15 ++--- .../subghz/scenes/subghz_scene_show_error.c | 2 +- applications/system/system_settings.c | 4 +- assets/compiled/assets_icons.c | 32 ---------- assets/compiled/assets_icons.h | 8 --- assets/icons/Dolphin/Flipper_young_80x60.png | Bin 643 -> 0 bytes assets/icons/Infrared/Back_15x10.png | Bin 3600 -> 0 bytes assets/icons/Infrared/Fill-marker_7x7.png | Bin 3595 -> 0 bytes assets/icons/Interface/Back3_45x8.png | Bin 3630 -> 0 bytes assets/icons/SDCard/SDError_43x35.png | Bin 1873 -> 0 bytes assets/icons/StatusBar/USBConnected_15x8.png | Bin 85 -> 0 bytes assets/icons/iButton/DolphinExcited_64x63.png | Bin 2304 -> 0 bytes .../iButton/iButtonDolphinSuccess_109x60.png | Bin 2178 -> 0 bytes .../targets/f7/furi_hal/furi_hal_resources.c | 2 +- 31 files changed, 122 insertions(+), 145 deletions(-) mode change 100755 => 100644 applications/power/power_service/views/power_off.c rename applications/storage_settings/scenes/{storage_settings_scene_eject_confirm.c => storage_settings_scene_unmount_confirm.c} (82%) rename applications/storage_settings/scenes/{storage_settings_scene_ejected.c => storage_settings_scene_unmounted.c} (76%) delete mode 100644 assets/icons/Dolphin/Flipper_young_80x60.png delete mode 100644 assets/icons/Infrared/Back_15x10.png delete mode 100644 assets/icons/Infrared/Fill-marker_7x7.png delete mode 100644 assets/icons/Interface/Back3_45x8.png delete mode 100644 assets/icons/SDCard/SDError_43x35.png delete mode 100644 assets/icons/StatusBar/USBConnected_15x8.png delete mode 100644 assets/icons/iButton/DolphinExcited_64x63.png delete mode 100644 assets/icons/iButton/iButtonDolphinSuccess_109x60.png diff --git a/applications/bt/bt_service/bt.c b/applications/bt/bt_service/bt.c index 111ebcb2..4a7b6f65 100755 --- a/applications/bt/bt_service/bt.c +++ b/applications/bt/bt_service/bt.c @@ -80,7 +80,7 @@ static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { string_init_printf(pin_str, "Verify code\n%06d", pin); dialog_message_set_text( bt->dialog_message, string_get_cstr(pin_str), 64, 4, AlignCenter, AlignTop); - dialog_message_set_buttons(bt->dialog_message, "Cancel", "Ok", NULL); + dialog_message_set_buttons(bt->dialog_message, "Cancel", "OK", NULL); DialogMessageButton button = dialog_message_show(bt->dialogs, bt->dialog_message); string_clear(pin_str); return button == DialogMessageButtonCenter; diff --git a/applications/desktop/views/desktop_view_locked.c b/applications/desktop/views/desktop_view_locked.c index 9f39c2ce..4b544988 100644 --- a/applications/desktop/views/desktop_view_locked.c +++ b/applications/desktop/views/desktop_view_locked.c @@ -125,7 +125,9 @@ static void desktop_view_locked_draw(Canvas* canvas, void* model) { canvas_set_font(canvas, FontSecondary); elements_bold_rounded_frame(canvas, 14, 2 + STATUS_BAR_Y_SHIFT, 99, 48); elements_multiline_text(canvas, 65, 20 + STATUS_BAR_Y_SHIFT, "To unlock\npress:"); - canvas_draw_icon(canvas, 65, 36 + STATUS_BAR_Y_SHIFT, &I_Back3_45x8); + canvas_draw_icon(canvas, 65, 36 + STATUS_BAR_Y_SHIFT, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 80, 36 + STATUS_BAR_Y_SHIFT, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 95, 36 + STATUS_BAR_Y_SHIFT, &I_Pin_back_arrow_10x8); canvas_draw_icon(canvas, 16, 7 + STATUS_BAR_Y_SHIFT, &I_WarningDolphin_45x42); canvas_draw_dot(canvas, 17, 61); } else if(view_state == DesktopViewLockedStateUnlockedHintShown) { diff --git a/applications/gpio/scenes/gpio_scene_start.c b/applications/gpio/scenes/gpio_scene_start.c index 4257ec39..4df74114 100644 --- a/applications/gpio/scenes/gpio_scene_start.c +++ b/applications/gpio/scenes/gpio_scene_start.c @@ -15,8 +15,8 @@ enum GpioOtg { }; const char* const gpio_otg_text[GpioOtgSettingsNum] = { - "Off", - "On", + "OFF", + "ON", }; static void gpio_scene_start_var_list_enter_callback(void* context, uint32_t index) { diff --git a/applications/ibutton/scenes/ibutton_scene_read_crc_error.c b/applications/ibutton/scenes/ibutton_scene_read_crc_error.c index 4df96d64..28d59d2d 100644 --- a/applications/ibutton/scenes/ibutton_scene_read_crc_error.c +++ b/applications/ibutton/scenes/ibutton_scene_read_crc_error.c @@ -61,11 +61,7 @@ void ibutton_scene_read_crc_error_on_exit(void* context) { ibutton_text_store_clear(ibutton); - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff); } diff --git a/applications/ibutton/scenes/ibutton_scene_read_not_key_error.c b/applications/ibutton/scenes/ibutton_scene_read_not_key_error.c index 76f14dae..45fbefe8 100644 --- a/applications/ibutton/scenes/ibutton_scene_read_not_key_error.c +++ b/applications/ibutton/scenes/ibutton_scene_read_not_key_error.c @@ -62,11 +62,7 @@ void ibutton_scene_read_not_key_error_on_exit(void* context) { ibutton_text_store_clear(ibutton); - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff); } diff --git a/applications/ibutton/scenes/ibutton_scene_read_success.c b/applications/ibutton/scenes/ibutton_scene_read_success.c index c3a13478..1c2bcdd2 100644 --- a/applications/ibutton/scenes/ibutton_scene_read_success.c +++ b/applications/ibutton/scenes/ibutton_scene_read_success.c @@ -76,12 +76,7 @@ void ibutton_scene_read_success_on_exit(void* context) { ibutton_text_store_clear(ibutton); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); + dialog_ex_reset(dialog_ex); ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOff); } diff --git a/applications/infrared/view/infrared_progress_view.c b/applications/infrared/view/infrared_progress_view.c index c9075147..cd2a0754 100644 --- a/applications/infrared/view/infrared_progress_view.c +++ b/applications/infrared/view/infrared_progress_view.c @@ -59,7 +59,7 @@ static void infrared_progress_view_draw_callback(Canvas* canvas, void* _model) { elements_multiline_text_aligned( canvas, x + 33, y + 37, AlignCenter, AlignCenter, percents_string); - canvas_draw_icon(canvas, x + 11, y + height - 15, &I_Back_15x10); + canvas_draw_icon(canvas, x + 14, y + height - 14, &I_Pin_back_arrow_10x8); canvas_draw_str(canvas, x + 30, y + height - 6, "= stop"); } diff --git a/applications/power/power_service/power.c b/applications/power/power_service/power.c index ac1856e7..1315809e 100644 --- a/applications/power/power_service/power.c +++ b/applications/power/power_service/power.c @@ -168,10 +168,24 @@ static void power_check_low_battery(Power* power) { } // If battery low, update view and switch off power after timeout if(power->battery_low) { - if(power->power_off_timeout) { - power_off_set_time_left(power->power_off, power->power_off_timeout--); - } else { + PowerOffResponse response = power_off_get_response(power->power_off); + if(response == PowerOffResponseDefault) { + if(power->power_off_timeout) { + power_off_set_time_left(power->power_off, power->power_off_timeout--); + } else { + power_off(power); + } + } else if(response == PowerOffResponseOk) { power_off(power); + } else if(response == PowerOffResponseHide) { + view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE); + if(power->power_off_timeout) { + power_off_set_time_left(power->power_off, power->power_off_timeout--); + } else { + power_off(power); + } + } else if(response == PowerOffResponseCancel) { + view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE); } } } diff --git a/applications/power/power_service/views/power_off.c b/applications/power/power_service/views/power_off.c old mode 100755 new mode 100644 index 46344b9b..398ebe4a --- a/applications/power/power_service/views/power_off.c +++ b/applications/power/power_service/views/power_off.c @@ -7,6 +7,7 @@ struct PowerOff { }; typedef struct { + PowerOffResponse response; uint32_t time_left_sec; } PowerOffModel; @@ -21,18 +22,54 @@ static void power_off_draw_callback(Canvas* canvas, void* _model) { canvas_draw_icon(canvas, 0, 18, &I_BatteryBody_52x28); canvas_draw_icon(canvas, 16, 25, &I_FaceNopower_29x14); elements_bubble(canvas, 54, 17, 70, 30); + canvas_set_font(canvas, FontSecondary); - elements_multiline_text_aligned( - canvas, 70, 23, AlignLeft, AlignTop, "Connect me\n to charger."); - snprintf(buff, sizeof(buff), "Poweroff in %lds.", model->time_left_sec); - canvas_draw_str_aligned(canvas, 64, 60, AlignCenter, AlignBottom, buff); + if(model->response == PowerOffResponseDefault) { + snprintf(buff, sizeof(buff), "Charge me!\nOff in %lds!", model->time_left_sec); + elements_multiline_text_aligned(canvas, 70, 23, AlignLeft, AlignTop, buff); + + elements_button_left(canvas, "Cancel"); + elements_button_center(canvas, "OK"); + elements_button_right(canvas, "Hide"); + } else { + snprintf(buff, sizeof(buff), "Charge me!\nDont't forget!"); + elements_multiline_text_aligned(canvas, 70, 23, AlignLeft, AlignTop, buff); + + canvas_draw_str_aligned(canvas, 64, 60, AlignCenter, AlignBottom, "Hold a second..."); + } +} + +static bool power_off_input_callback(InputEvent* event, void* context) { + PowerOff* power_off = context; + + bool consumed = false; + PowerOffModel* model = view_get_model(power_off->view); + if(model->response == PowerOffResponseDefault && event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + model->response = PowerOffResponseOk; + consumed = true; + } else if(event->key == InputKeyLeft) { + model->response = PowerOffResponseCancel; + consumed = true; + } else if(event->key == InputKeyRight) { + model->response = PowerOffResponseHide; + consumed = true; + } + } + view_commit_model(power_off->view, consumed); + + return true; } PowerOff* power_off_alloc() { PowerOff* power_off = malloc(sizeof(PowerOff)); + power_off->view = view_alloc(); view_allocate_model(power_off->view, ViewModelTypeLocking, sizeof(PowerOffModel)); + view_set_context(power_off->view, power_off); view_set_draw_callback(power_off->view, power_off_draw_callback); + view_set_input_callback(power_off->view, power_off_input_callback); + return power_off; } @@ -55,3 +92,14 @@ void power_off_set_time_left(PowerOff* power_off, uint8_t time_left) { return true; }); } + +PowerOffResponse power_off_get_response(PowerOff* power_off) { + furi_assert(power_off); + PowerOffResponse response; + with_view_model( + power_off->view, (PowerOffModel * model) { + response = model->response; + return false; + }); + return response; +} diff --git a/applications/power/power_service/views/power_off.h b/applications/power/power_service/views/power_off.h index 2e2e91f7..5137c2e9 100644 --- a/applications/power/power_service/views/power_off.h +++ b/applications/power/power_service/views/power_off.h @@ -2,6 +2,13 @@ typedef struct PowerOff PowerOff; +typedef enum { + PowerOffResponseDefault, + PowerOffResponseOk, + PowerOffResponseCancel, + PowerOffResponseHide, +} PowerOffResponse; + #include PowerOff* power_off_alloc(); @@ -11,3 +18,5 @@ void power_off_free(PowerOff* power_off); View* power_off_get_view(PowerOff* power_off); void power_off_set_time_left(PowerOff* power_off, uint8_t time_left); + +PowerOffResponse power_off_get_response(PowerOff* power_off); diff --git a/applications/storage_settings/scenes/storage_settings_scene_benchmark.c b/applications/storage_settings/scenes/storage_settings_scene_benchmark.c index e7cfd826..45fbce8f 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -151,13 +151,7 @@ void storage_settings_scene_benchmark_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); string_reset(app->text_string); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_factory_reset.c b/applications/storage_settings/scenes/storage_settings_scene_factory_reset.c index 78a8363a..84119422 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_factory_reset.c +++ b/applications/storage_settings/scenes/storage_settings_scene_factory_reset.c @@ -18,7 +18,7 @@ void storage_settings_scene_factory_reset_on_enter(void* context) { dialog_ex_set_context(dialog_ex, app); dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_factory_reset_dialog_callback); - dialog_ex_set_left_button_text(dialog_ex, "Back"); + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Erase"); dialog_ex_set_header(dialog_ex, "Confirm Factory Reset", 64, 10, AlignCenter, AlignCenter); @@ -70,7 +70,10 @@ bool storage_settings_scene_factory_reset_on_event(void* context, SceneManagerEv consumed = true; break; } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; } + return consumed; } @@ -78,13 +81,7 @@ void storage_settings_scene_factory_reset_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); string_reset(app->text_string); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c b/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c index a80215bc..db040d6e 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c +++ b/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c @@ -57,11 +57,5 @@ void storage_settings_scene_format_confirm_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_formatting.c b/applications/storage_settings/scenes/storage_settings_scene_formatting.c index 143bda95..2cbf97ee 100755 --- a/applications/storage_settings/scenes/storage_settings_scene_formatting.c +++ b/applications/storage_settings/scenes/storage_settings_scene_formatting.c @@ -39,18 +39,17 @@ void storage_settings_scene_formatting_on_enter(void* context) { notification_message(app->notification, &sequence_reset_formatting_leds); notification_message(app->notification, &sequence_blink_green_100); + dialog_ex_set_context(dialog_ex, app); + dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_formatting_dialog_callback); + if(error != FSE_OK) { dialog_ex_set_header(dialog_ex, "Cannot format SD Card", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_text( dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); } else { - dialog_ex_set_header(dialog_ex, "SD card formatted", 64, 10, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, "Press back to return", 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Format complete!", 64, 32, AlignCenter, AlignCenter); } - - dialog_ex_set_context(dialog_ex, app); - dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_formatting_dialog_callback); - dialog_ex_set_left_button_text(dialog_ex, "Back"); + dialog_ex_set_center_button_text(dialog_ex, "OK"); } bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent event) { @@ -59,14 +58,13 @@ bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case DialogExResultLeft: + case DialogExResultCenter: consumed = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, StorageSettingsStart); break; } } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, StorageSettingsStart); + consumed = true; } return consumed; @@ -76,11 +74,5 @@ void storage_settings_scene_formatting_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_internal_info.c b/applications/storage_settings/scenes/storage_settings_scene_internal_info.c index 971695b6..53f791bd 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_internal_info.c +++ b/applications/storage_settings/scenes/storage_settings_scene_internal_info.c @@ -18,7 +18,6 @@ void storage_settings_scene_internal_info_on_enter(void* context) { dialog_ex_set_context(dialog_ex, app); dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_internal_info_dialog_callback); - dialog_ex_set_left_button_text(dialog_ex, "Back"); if(error != FSE_OK) { dialog_ex_set_header( dialog_ex, "Internal storage error", 64, 10, AlignCenter, AlignCenter); @@ -56,13 +55,7 @@ void storage_settings_scene_internal_info_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); string_reset(app->text_string); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_sd_info.c b/applications/storage_settings/scenes/storage_settings_scene_sd_info.c index 297041a5..b64caeb2 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_sd_info.c +++ b/applications/storage_settings/scenes/storage_settings_scene_sd_info.c @@ -15,7 +15,6 @@ void storage_settings_scene_sd_info_on_enter(void* context) { dialog_ex_set_context(dialog_ex, app); dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_sd_info_dialog_callback); - dialog_ex_set_left_button_text(dialog_ex, "Back"); if(sd_status != FSE_OK) { dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_text( @@ -62,13 +61,7 @@ void storage_settings_scene_sd_info_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); string_reset(app->text_string); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_eject_confirm.c b/applications/storage_settings/scenes/storage_settings_scene_unmount_confirm.c similarity index 82% rename from applications/storage_settings/scenes/storage_settings_scene_eject_confirm.c rename to applications/storage_settings/scenes/storage_settings_scene_unmount_confirm.c index 8fc6c871..f9499322 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_eject_confirm.c +++ b/applications/storage_settings/scenes/storage_settings_scene_unmount_confirm.c @@ -11,9 +11,9 @@ void storage_settings_scene_unmount_confirm_on_enter(void* context) { StorageSettings* app = context; FS_Error sd_status = storage_sd_status(app->fs_api); DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_left_button_text(dialog_ex, "Back"); if(sd_status == FSE_NOT_READY) { + dialog_ex_set_center_button_text(dialog_ex, "OK"); dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_text( dialog_ex, @@ -23,6 +23,7 @@ void storage_settings_scene_unmount_confirm_on_enter(void* context) { AlignCenter, AlignCenter); } else { + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Unmount"); dialog_ex_set_header(dialog_ex, "Unmount SD card?", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_text( @@ -42,6 +43,9 @@ bool storage_settings_scene_unmount_confirm_on_event(void* context, SceneManager if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { + case DialogExResultCenter: + consumed = scene_manager_previous_scene(app->scene_manager); + break; case DialogExResultLeft: consumed = scene_manager_previous_scene(app->scene_manager); break; @@ -50,7 +54,10 @@ bool storage_settings_scene_unmount_confirm_on_event(void* context, SceneManager consumed = true; break; } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; } + return consumed; } @@ -58,11 +65,5 @@ void storage_settings_scene_unmount_confirm_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); } diff --git a/applications/storage_settings/scenes/storage_settings_scene_ejected.c b/applications/storage_settings/scenes/storage_settings_scene_unmounted.c similarity index 76% rename from applications/storage_settings/scenes/storage_settings_scene_ejected.c rename to applications/storage_settings/scenes/storage_settings_scene_unmounted.c index 08208f87..ddd70d05 100755 --- a/applications/storage_settings/scenes/storage_settings_scene_ejected.c +++ b/applications/storage_settings/scenes/storage_settings_scene_unmounted.c @@ -12,7 +12,7 @@ void storage_settings_scene_unmounted_on_enter(void* context) { FS_Error error = storage_sd_unmount(app->fs_api); DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_left_button_text(dialog_ex, "Back"); + dialog_ex_set_center_button_text(dialog_ex, "OK"); if(error == FSE_OK) { dialog_ex_set_header(dialog_ex, "SD card unmounted", 64, 10, AlignCenter, AlignCenter); @@ -39,14 +39,13 @@ bool storage_settings_scene_unmounted_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case DialogExResultLeft: + case DialogExResultCenter: consumed = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, StorageSettingsStart); break; } } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, StorageSettingsStart); + consumed = true; } return consumed; @@ -56,11 +55,5 @@ void storage_settings_scene_unmounted_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, NULL); - dialog_ex_set_right_button_text(dialog_ex, NULL); - dialog_ex_set_result_callback(dialog_ex, NULL); - dialog_ex_set_context(dialog_ex, NULL); + dialog_ex_reset(dialog_ex); } diff --git a/applications/subghz/scenes/subghz_scene_show_error.c b/applications/subghz/scenes/subghz_scene_show_error.c index 2998bf8e..5632a859 100644 --- a/applications/subghz/scenes/subghz_scene_show_error.c +++ b/applications/subghz/scenes/subghz_scene_show_error.c @@ -37,7 +37,7 @@ void subghz_scene_show_error_on_enter(void* context) { if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) == SubGhzCustomEventManagerSet) { widget_add_button_element( - subghz->widget, GuiButtonTypeRight, "Ok", subghz_scene_show_error_callback, subghz); + subghz->widget, GuiButtonTypeRight, "OK", subghz_scene_show_error_callback, subghz); } else { notification_message(subghz->notifications, &subghs_sequence_sd_error); } diff --git a/applications/system/system_settings.c b/applications/system/system_settings.c index 97017c8d..7bbcdd7b 100644 --- a/applications/system/system_settings.c +++ b/applications/system/system_settings.c @@ -30,8 +30,8 @@ static void log_level_changed(VariableItem* item) { } const char* const debug_text[] = { - "Disable", - "Enable", + "OFF", + "ON", }; static void debug_changed(VariableItem* item) { diff --git a/assets/compiled/assets_icons.c b/assets/compiled/assets_icons.c index 709fab45..9046dddd 100644 --- a/assets/compiled/assets_icons.c +++ b/assets/compiled/assets_icons.c @@ -190,9 +190,6 @@ const uint8_t* const _I_DolphinFirstStart8_56x51[] = {_I_DolphinFirstStart8_56x5 const uint8_t _I_DolphinOkay_41x43_0[] = {0x01,0x00,0xa0,0x00,0x00,0x0f,0x82,0x3e,0x05,0x38,0xf7,0x80,0x08,0x58,0x08,0x0c,0x02,0x0e,0x05,0x1b,0x00,0x08,0x63,0x00,0x21,0x88,0x00,0x86,0x40,0x02,0x18,0x40,0x08,0x68,0x00,0x21,0x82,0x06,0x88,0x0a,0xf0,0x21,0x39,0x09,0x84,0x02,0x20,0x57,0x09,0x98,0x15,0x67,0xc0,0x54,0xbe,0x81,0x4f,0x01,0xfe,0x02,0x9d,0x03,0xc4,0x20,0x10,0x29,0x7c,0x80,0xa9,0xfe,0x02,0xac,0x14,0x0a,0x77,0xc8,0x58,0x8c,0xf0,0x11,0x51,0x79,0xff,0x61,0x44,0x93,0x81,0x02,0xc4,0x9e,0x60,0xb2,0xf0,0xa0,0x46,0x0c,0x17,0x14,0x99,0x1a,0x07,0x80,0x59,0x49,0x82,0x21,0xc0,0xa4,0x82,0x24,0xb9,0x20,0x88,0x1c,0x47,0xc2,0x07,0x11,0x54,0xa0,0x60,0x53,0xb8,0x0a,0x4b,0xf3,0x03,0x87,0x81,0x4a,0x0d,0xfc,0x1a,0x98,0x68,0xb8,0x01,0x51,0x13,0x15,0xe0,0x82,0x7f,0x8d,0x78,0x38,0xbf,0xff,0xfa,0xb8,0x60,0xbf,0x1b,0xf9,0x50,0x14,0xea,0xe7,0x02,0x02,0x8e,0xac,0x94,0x40,}; const uint8_t* const _I_DolphinOkay_41x43[] = {_I_DolphinOkay_41x43_0}; -const uint8_t _I_Flipper_young_80x60_0[] = {0x01,0x00,0xa3,0x01,0x00,0x1e,0x03,0xff,0xff,0x87,0x82,0x57,0xf1,0x83,0x90,0xde,0x01,0x2b,0x0e,0x83,0x70,0xfb,0x10,0x10,0x41,0xf8,0x27,0x70,0xcc,0x34,0xc6,0x0e,0x09,0x3e,0x04,0x86,0x21,0x0c,0x90,0xc3,0x03,0xa9,0xe7,0xb0,0x46,0x2c,0x51,0x40,0x4a,0x63,0x38,0x31,0x0a,0x34,0x90,0x12,0x91,0x8e,0x3c,0xff,0x89,0x4c,0x04,0xa4,0x43,0xfd,0xf3,0xc3,0xf2,0x01,0x29,0xe0,0x2b,0x8e,0x72,0xa0,0x46,0x4b,0xe0,0x30,0xba,0x10,0x22,0xca,0x1c,0x0b,0x26,0x09,0x3c,0x04,0x0c,0x08,0x59,0xc8,0x21,0x64,0xc4,0x47,0x98,0x82,0x81,0x0a,0xe0,0x21,0x39,0x04,0x34,0x88,0x60,0x93,0xa0,0x45,0x4b,0x06,0xa3,0x40,0x48,0xfc,0x20,0xf0,0x82,0xa2,0x4d,0x60,0x11,0xe9,0xc2,0x19,0x64,0xd0,0x08,0x1f,0x80,0x7e,0x60,0x01,0x92,0x60,0x20,0x38,0x05,0x21,0x7c,0x3f,0xf0,0x1a,0xe6,0x00,0xe6,0x21,0x32,0x1a,0x0c,0x0e,0x91,0x80,0x8f,0xc0,0x06,0x25,0xcc,0xbf,0xc1,0xaa,0x10,0x0b,0xfc,0x02,0x60,0x2e,0x2c,0x04,0x32,0xc1,0x00,0xff,0x40,0x68,0x00,0x91,0x89,0xc0,0x21,0x20,0x51,0xfe,0x41,0xf0,0x00,0x91,0xc4,0xcf,0xe2,0x40,0x51,0xfc,0x0c,0x86,0x07,0x80,0xe2,0xdf,0xda,0x25,0xf0,0x9f,0xc0,0x21,0x98,0x0f,0x27,0xfd,0xa2,0x5e,0x01,0x90,0xc4,0x30,0x1e,0x2f,0xfc,0xa1,0x3a,0x45,0x41,0xb0,0x60,0x3e,0x5e,0x79,0x4a,0x10,0xbf,0xe2,0x61,0xc0,0x82,0x52,0x01,0xff,0x36,0x8e,0x3b,0xe5,0xff,0x04,0x9f,0xf8,0x78,0x3b,0x8f,0x97,0xf8,0x12,0x7f,0xc3,0x78,0xf8,0x3e,0x5f,0xc0,0x49,0xfe,0x08,0xc2,0x17,0x1f,0xcd,0xa5,0xac,0x5f,0x02,0x30,0xc0,0x30,0x5f,0xfd,0x23,0xbc,0xbc,0x1f,0xf0,0xc1,0x5f,0xaa,0x8e,0x52,0x28,0x10,0x10,0x6f,0x1b,0x28,0x57,0x81,0x66,0x25,0x01,0x80,0x4e,0x28,0x15,0x98,0xad,0xc3,0xfd,0xff,0xff,0x91,0x87,0xc1,0x80,0xd4,0xc2,0xb2,0x03,0xb1,0x5b,0x13,0x34,0x6a,0xf1,0x58,0x84,0x0e,0x1d,0x00,0x23,0x14,0x0f,0x55,0x0a,0x88,0x67,0x0d,0x83,0x7c,0x04,0x8c,0x0a,0xa9,0x15,0x90,0x7c,0x07,0x23,0xf8,0x80,0xc1,0xa0,0xda,0x88,0x54,0x82,0x00,0x2f,0x1f,0xe4,0x3c,0x7a,0x35,0x08,0xab,0x20,0x7f,0x03,0xc1,0x2d,0x96,0x82,0x14,0xce,0x20,0x02,0x04,0xc6,0x00,0x60,0x20,0x01,0x84,0xc4,0x6a,0x21,0x36,0x3b,0x8c,0xf0,0x3c,0xc8,0x02,0x1b,0x88,0x01,0xe1,0x80,0x98,0x2d,0x10,0x01,0xb0,0x05,0xa1,0x00,0x3d,0xf8,0x13,0x17,0x81,0x47,0x80,0x0b,0xc0,0x28,0x8e,0x02,0xa4,0x81,0x2c,0xf0,0x20,0x01,0x00,}; -const uint8_t* const _I_Flipper_young_80x60[] = {_I_Flipper_young_80x60_0}; - const uint8_t _I_ArrowDownEmpty_14x15_0[] = {0x01,0x00,0x17,0x00,0xfc,0x41,0xe1,0x10,0x40,0x0c,0xc3,0xe7,0x90,0x19,0x04,0x0a,0x20,0x08,0x10,0x48,0xc4,0x20,0x52,0x08,0x0f,0x02,0x00,}; const uint8_t* const _I_ArrowDownEmpty_14x15[] = {_I_ArrowDownEmpty_14x15_0}; @@ -205,9 +202,6 @@ const uint8_t* const _I_ArrowUpEmpty_14x15[] = {_I_ArrowUpEmpty_14x15_0}; const uint8_t _I_ArrowUpFilled_14x15_0[] = {0x00,0xC0,0x00,0x20,0x01,0xD0,0x02,0xE8,0x05,0xF4,0x0B,0xFA,0x17,0x61,0x21,0xAF,0x3D,0x68,0x05,0xA8,0x05,0x68,0x05,0xA8,0x05,0xE8,0x05,0x08,0x04,0xF8,0x07,}; const uint8_t* const _I_ArrowUpFilled_14x15[] = {_I_ArrowUpFilled_14x15_0}; -const uint8_t _I_Back_15x10_0[] = {0x00,0x04,0x00,0x06,0x00,0xFF,0x0F,0x06,0x10,0x04,0x20,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x10,0xFE,0x0F,}; -const uint8_t* const _I_Back_15x10[] = {_I_Back_15x10_0}; - const uint8_t _I_DolphinReadingSuccess_59x63_0[] = {0x01,0x00,0x19,0x01,0x00,0x1d,0x00,0x0f,0xd2,0x00,0x21,0xe0,0x3f,0xf0,0xf9,0x00,0x40,0xee,0x00,0x11,0x88,0x04,0x0e,0x18,0x11,0x18,0x8c,0x40,0x0e,0x50,0x30,0x10,0xc0,0xa1,0x01,0xe2,0x05,0x14,0x12,0x08,0x33,0x58,0x44,0x08,0x66,0xa1,0xe3,0x01,0x9c,0x83,0x00,0x24,0x11,0x11,0x06,0xc4,0x76,0x20,0x75,0x15,0x99,0x48,0xc0,0xe9,0x0f,0x03,0x95,0xfc,0x86,0x3c,0x09,0x80,0x1c,0x7c,0x00,0x91,0x81,0x48,0x2f,0xc1,0x41,0x8c,0xc0,0x20,0x30,0x1c,0x87,0xfc,0x0e,0x30,0x70,0x70,0x81,0xc7,0xe6,0x07,0x18,0x08,0x1c,0xb9,0x1e,0x38,0x0f,0x02,0x01,0xf0,0x03,0xa0,0xa4,0x7f,0x90,0x30,0x38,0xff,0xe0,0x28,0x21,0xff,0x06,0x44,0x0e,0x46,0xe1,0x01,0x8c,0x03,0x34,0x2f,0x25,0x18,0x80,0xc7,0x2a,0x03,0x2e,0x01,0x3c,0x70,0x12,0xa2,0x39,0x78,0x27,0xe0,0x31,0xea,0x82,0xc4,0x6c,0x31,0xf0,0x78,0xea,0xb0,0x22,0x31,0xfc,0x1a,0xc6,0x01,0x55,0x25,0x88,0xf8,0x4b,0x02,0x1f,0x13,0xe1,0x7f,0x97,0x85,0x15,0x03,0x90,0xf8,0xa0,0x10,0xa1,0xb1,0x0e,0x88,0x00,0x7f,0x0f,0xc0,0x7c,0x57,0x27,0x3c,0xb0,0x7f,0x5f,0xa9,0x1f,0xc0,0x6a,0xc5,0x05,0xc0,0xf0,0x11,0x46,0xac,0x18,0x3f,0xf9,0x54,0x75,0x00,0x73,0x1f,0x0f,0xfe,0xfe,0xc6,0x30,0x01,0xbc,0x48,0x00,0x84,0x82,0x00,0x1b,0x64,0xc0,0x07,0x60,0x03,0xb4,0x70,0x0c,0xbf,0x82,0x31,0x01,0x8d,0x0c,0x40,0x02,0x37,0x08,0x1d,0x74,0x00,0x76,0xa0,0x01,0xdb,0x01,0xfe,0x85,0x8b,0x96,0xaa,0x9b,0x30,0x01,0x6a,0xa3,0x40,0x75,0xaa,0x03,0xdb,0x50,0xbb,0x30,0x01,0x54,0x24,0x25,0xe6,0x51,0x08,0x1f,0x68,0x00,0x7f,0x03,0xf2,0x79,0xc0,0xf4,}; const uint8_t* const _I_DolphinReadingSuccess_59x63[] = {_I_DolphinReadingSuccess_59x63_0}; @@ -217,9 +211,6 @@ const uint8_t* const _I_Down_25x27[] = {_I_Down_25x27_0}; const uint8_t _I_Down_hvr_25x27_0[] = {0x01,0x00,0x3a,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x20,0x3f,0x01,0x9c,0x3e,0x01,0xe0,0x01,0xa4,0x7e,0x01,0xf0,0x80,0x8b,0x47,0xf1,0x01,0x16,0x8f,0xf0,0x2e,0x23,0x11,0x01,0x88,0x04,0xf0,0x60,0x32,0xe3,0x80,0xcb,0xde,0x37,0xf0,0x1a,0x95,0xcc,0xbe,0x66,0x73,}; const uint8_t* const _I_Down_hvr_25x27[] = {_I_Down_hvr_25x27_0}; -const uint8_t _I_Fill_marker_7x7_0[] = {0x00,0x1C,0x32,0x6F,0x5F,0x7F,0x3E,0x1C,}; -const uint8_t* const _I_Fill_marker_7x7[] = {_I_Fill_marker_7x7_0}; - const uint8_t _I_InfraredArrowDown_4x8_0[] = {0x00,0xFF,0x7E,0x3C,0x18,}; const uint8_t* const _I_InfraredArrowDown_4x8[] = {_I_InfraredArrowDown_4x8_0}; @@ -268,9 +259,6 @@ const uint8_t* const _I_Vol_up_25x27[] = {_I_Vol_up_25x27_0}; const uint8_t _I_Vol_up_hvr_25x27_0[] = {0x01,0x00,0x28,0x00,0xfc,0x7f,0xe7,0xf0,0x0f,0xe7,0xfe,0xff,0x00,0xff,0x7f,0xff,0xf0,0x00,0x10,0xff,0xe0,0x20,0x38,0xf7,0x80,0xfc,0x06,0xa2,0xd1,0xfc,0x00,0xd0,0x2f,0xe0,0x38,0x21,0xd8,0x0c,0x8a,0xe6,0x5f,0x33,0x39,0x80,}; const uint8_t* const _I_Vol_up_hvr_25x27[] = {_I_Vol_up_hvr_25x27_0}; -const uint8_t _I_Back3_45x8_0[] = {0x00,0x04,0x00,0x10,0x00,0x40,0x00,0x06,0x00,0x18,0x00,0x60,0x00,0x7F,0x00,0xFC,0x01,0xF0,0x07,0x86,0x20,0x18,0x82,0x60,0x08,0x04,0x71,0x10,0xC4,0x41,0x10,0x00,0x21,0x00,0x84,0x00,0x10,0x80,0x00,0x00,0x02,0x00,0x08,0x7E,0x00,0xF8,0x01,0xE0,0x07,}; -const uint8_t* const _I_Back3_45x8[] = {_I_Back3_45x8_0}; - const uint8_t _I_DoorLeft_70x55_0[] = {0x01,0x00,0x19,0x01,0x00,0x2c,0x32,0x01,0x03,0x04,0x2c,0x18,0x10,0xf0,0x40,0x47,0x82,0x06,0x81,0x03,0xff,0x80,0x08,0x1a,0x20,0x82,0x15,0x28,0x21,0x87,0x82,0x08,0x6f,0xc0,0xb1,0xe6,0x10,0x10,0x8b,0x46,0x20,0x43,0x55,0x8f,0x82,0x10,0x32,0x73,0x0a,0x09,0x89,0x6c,0x1e,0x09,0x00,0x18,0x60,0xf0,0x0c,0x84,0x93,0x82,0x03,0x18,0x0c,0x02,0x1d,0x00,0x90,0x52,0x70,0x50,0x1e,0x00,0x58,0x63,0x90,0x0a,0x06,0x4a,0x09,0x03,0xb0,0x02,0x06,0x70,0x62,0x49,0xf8,0x0c,0x66,0x3f,0xf0,0x41,0x63,0x04,0x43,0x00,0x99,0x60,0x00,0x85,0xc8,0x06,0x14,0xd0,0x80,0x3f,0xc8,0x0d,0xb8,0x10,0x70,0xf8,0x34,0x13,0x03,0x39,0x04,0x1c,0x42,0x19,0xf8,0xa0,0xc2,0x01,0x07,0xef,0x02,0x8c,0x80,0x10,0x9d,0x00,0x43,0xec,0x00,0xa3,0x10,0x04,0x25,0xce,0x19,0xfc,0x88,0x82,0x12,0x0c,0x35,0x10,0x42,0x4c,0xa1,0x90,0x3f,0xc0,0x21,0x22,0x39,0x82,0xc8,0x88,0xd2,0x11,0xf0,0x01,0x88,0xd5,0x18,0xe2,0x08,0x68,0x10,0x0c,0xa8,0x00,0x83,0x81,0xcc,0xd5,0xc3,0x80,0x84,0x82,0x0e,0xcc,0xc0,0x15,0x79,0x02,0x0b,0x98,0xf8,0x11,0x88,0x82,0x0f,0x31,0x19,0x02,0x08,0x2c,0x9f,0x6a,0x1d,0x20,0x41,0x31,0x4c,0x10,0x8d,0x73,0x04,0x23,0xa4,0xc4,0x6c,0xde,0x20,0x42,0xcc,0x01,0x07,0x07,0xff,0x80,0x06,0x3e,0x08,0x38,0x70,0x20,0xa1,0xe0,0x83,0x8e,0x01,0x0c,0xf0,0x73,0x80,0x43,0x70,0x05,0x08,0x00,0x2c,0x04,0xc4,0x46,0x53,0x09,0x98,0x24,0x80,0x65,0x80,0xb0,0xd9,0x84,0x65,0x32,0x06,0x17,0x0f,0x98,0x23,0x63,0xe1,0x88,0xc4,0x08,0x5f,0xc1,0x30,0x9d,0x84,0x4e,0x66,0x94,0x11,0x98,0x75,0x26,0x00,}; const uint8_t* const _I_DoorLeft_70x55[] = {_I_DoorLeft_70x55_0}; @@ -558,9 +546,6 @@ const uint8_t* const _I_RFIDDolphinSend_97x61[] = {_I_RFIDDolphinSend_97x61_0}; const uint8_t _I_RFIDDolphinSuccess_108x57_0[] = {0x01,0x00,0xe7,0x01,0x00,0x0f,0x03,0xff,0x1f,0x06,0xd4,0xe2,0x01,0xe0,0x06,0xd4,0x18,0x04,0x30,0x30,0x64,0x60,0x20,0x20,0x31,0x86,0x03,0x62,0x80,0x03,0x28,0x80,0x36,0x24,0x00,0x36,0x00,0x28,0x5c,0xc3,0xe6,0x00,0x58,0x40,0xec,0xc1,0xb1,0x04,0x02,0x19,0x24,0x80,0x0b,0x02,0x02,0x40,0x37,0xc4,0x8c,0x2e,0x40,0x6f,0x93,0x8b,0x81,0x07,0x06,0xdc,0xc2,0x38,0x66,0x50,0x6a,0xe2,0x27,0xe0,0xd2,0xfc,0x08,0x09,0x0c,0x9c,0x4b,0x98,0x34,0xa0,0xe1,0xd5,0x06,0x8f,0x92,0xc2,0x05,0x1e,0x42,0xe1,0x81,0xa3,0xe2,0xf0,0xbc,0x4c,0x1a,0xff,0x2f,0x9b,0x80,0xd8,0xca,0x05,0x1f,0x97,0xfd,0xf8,0x60,0xd2,0x01,0x1e,0x00,0x1a,0x5c,0x00,0x08,0xc9,0xc1,0xab,0x40,0xf9,0x83,0x46,0x61,0x00,0xd8,0x4a,0x81,0xab,0xa0,0xf3,0x5f,0xc6,0x05,0x58,0x8a,0xa4,0x09,0x76,0x21,0xb1,0xf2,0x83,0x4f,0x5d,0x1a,0x01,0x8c,0x90,0x1a,0x31,0x0d,0x07,0xa9,0x16,0x50,0x0a,0xac,0x34,0xba,0x42,0xa1,0x88,0x50,0x23,0xaa,0x72,0xe0,0x6a,0xa1,0x4a,0x32,0x39,0x88,0x6c,0x60,0xc7,0x82,0xb0,0x55,0x60,0xa2,0x92,0x80,0xc0,0x43,0x63,0x03,0x25,0x96,0xe3,0x54,0x33,0x18,0xc4,0x90,0x22,0x21,0x81,0x81,0x03,0x4a,0xa9,0x55,0x7a,0x17,0xf3,0x82,0x9f,0x6d,0x5e,0xa9,0xb6,0x50,0x38,0x70,0x35,0x70,0x15,0x5a,0xa9,0xb8,0xa3,0x46,0x12,0x06,0x9f,0x83,0x54,0x8a,0x28,0x80,0x34,0xfc,0x08,0x93,0xaa,0xc7,0x40,0x83,0x83,0x81,0xd3,0xa1,0xd1,0x08,0x84,0x0c,0x24,0x3f,0xed,0x54,0x18,0x26,0x50,0x20,0xd9,0x42,0x21,0x90,0x4c,0x07,0xff,0xae,0x52,0x20,0x6a,0xc4,0x23,0x1f,0x88,0x3f,0xf0,0x1a,0x45,0x31,0xe7,0x03,0x4a,0x41,0xe0,0x69,0x0f,0xc2,0x1e,0x0d,0x19,0x80,0x48,0xa2,0x10,0xc5,0x68,0xdf,0x0a,0x82,0xb9,0x28,0x22,0x2c,0xe3,0x0a,0xd1,0x2b,0x0f,0x00,0x3c,0x22,0x91,0x53,0x9c,0x50,0x1a,0x30,0x08,0x39,0x1c,0x60,0x6d,0x12,0x3d,0x8c,0xc2,0x51,0x00,0x17,0x0c,0xe2,0x01,0xff,0x83,0x84,0xc6,0x40,0xb0,0x19,0x84,0xd0,0x1a,0x5c,0x08,0x1f,0xf8,0x8c,0x50,0x43,0x08,0xce,0x2d,0x06,0x71,0x5f,0x17,0xfe,0x12,0xdf,0x20,0x69,0x55,0x01,0xa6,0x00,0x18,0x40,0xa4,0x80,0x63,0x3c,0xb5,0x03,0x56,0x08,0x8b,0x20,0x10,0xcf,0x03,0x62,0x08,0x20,0x00,0x94,0xc6,0x01,0x70,0x01,0x0c,0xe8,0x36,0x20,0xd3,0xe0,0x00,0xcb,0x10,0x02,0x19,0xf3,0x9c,0x41,0xa3,0x15,0x31,0x90,0x00,0x70,0xc0,0x21,0xdd,0x86,0xc4,0x78,0x3e,0xa3,0x71,0xe0,0x30,0x20,0x31,0xbe,0x86,0xc4,0x1a,0x35,0x40,0x20,0x8d,0x89,0x28,0x5b,0xa0,0xd9,0xea,0x3d,0x44,0x42,0x87,0x83,0x48,0x36,0x49,0xe1,0xa0,0x75,0x67,0x8d,0x41,0x54,0x14,0x03,0xf5,0x2a,0x06,0x96,0x03,0x54,0xc4,0x14,0xd0,0x83,0x4a,0xfb,0x35,0x06,0x90,0x38,0x4e,0x46,0xb4,0x10,0xd9,0x81,0x49,0x72,0x40,0x01,0x0a,0x95,0xd4,0x36,0x20,0xd7,0x55,0x10,}; const uint8_t* const _I_RFIDDolphinSuccess_108x57[] = {_I_RFIDDolphinSuccess_108x57_0}; -const uint8_t _I_SDError_43x35_0[] = {0x01,0x00,0x6f,0x00,0xff,0x7f,0xc0,0x05,0x03,0x80,0x82,0x8e,0x08,0x05,0x59,0xe8,0x16,0x82,0x2d,0x30,0x8c,0x43,0x20,0xc0,0x51,0xb0,0x43,0x23,0x10,0x30,0x88,0xf0,0x20,0xdb,0x08,0x08,0x2c,0x70,0x10,0x3f,0x00,0x5c,0x80,0xa8,0x11,0x60,0xea,0x0a,0x54,0x8f,0xe5,0x99,0xfe,0x4f,0xc0,0xa6,0x70,0x10,0x89,0x60,0x23,0xff,0x91,0xa9,0x70,0x25,0xff,0x21,0xa9,0x70,0x2b,0xfe,0x42,0xc9,0x60,0x30,0x7e,0x40,0x89,0x42,0x30,0x12,0x08,0x80,0x14,0xa0,0x11,0x10,0x28,0xc0,0x66,0x10,0x08,0x74,0x30,0x8f,0xe0,0x58,0x5c,0x88,0x14,0xc0,0x43,0x01,0x8f,0x81,0x4f,0x05,0x20,0x02,0x9f,0xf3,0x80,0xcb,0x10,}; -const uint8_t* const _I_SDError_43x35[] = {_I_SDError_43x35_0}; - const uint8_t _I_SDQuestion_35x43_0[] = {0x01,0x00,0x67,0x00,0xf8,0x7f,0xc0,0x03,0x03,0xfc,0x01,0x0a,0x0f,0x38,0xa4,0xe4,0xa4,0x80,0x4f,0x0c,0x20,0x13,0xc0,0x9f,0x80,0x02,0x15,0xfe,0x00,0x04,0x29,0xfc,0x03,0xfd,0x07,0xfa,0x47,0xe7,0xdf,0xc8,0x3f,0xea,0x1f,0x7f,0xfc,0x41,0xff,0xb8,0xff,0xf8,0x10,0x7f,0xe0,0x4e,0xef,0x86,0x08,0x68,0x33,0xf1,0x10,0xff,0x3f,0xf1,0xf1,0x60,0x81,0x06,0x1e,0x36,0x10,0x20,0xe1,0xc0,0x87,0xc7,0x02,0x0f,0xd3,0xff,0xe3,0x02,0x0f,0xe8,0x08,0x7f,0xd0,0x21,0x89,0xc4,0x08,0x9f,0x70,0x21,0x9a,0x08,0x08,0xc1,0x89,0x02,0x20,0x62,0x40,0x8f,0xfe,0x68,0x98,}; const uint8_t* const _I_SDQuestion_35x43[] = {_I_SDQuestion_35x43_0}; @@ -609,9 +594,6 @@ const uint8_t* const _I_SDcardFail_11x8[] = {_I_SDcardFail_11x8_0}; const uint8_t _I_SDcardMounted_11x8_0[] = {0x01,0x00,0x09,0x00,0xff,0xc1,0xff,0xf0,0x40,0x1c,0xd9,0xe0,0x00,}; const uint8_t* const _I_SDcardMounted_11x8[] = {_I_SDcardMounted_11x8_0}; -const uint8_t _I_USBConnected_15x8_0[] = {0x00,0xF0,0x07,0x08,0x7C,0x04,0x44,0x07,0x54,0x07,0x54,0x04,0x44,0x08,0x7C,0xF0,0x07,}; -const uint8_t* const _I_USBConnected_15x8[] = {_I_USBConnected_15x8_0}; - const uint8_t _I_Lock_7x8_0[] = {0x00,0x1C,0x22,0x22,0x7F,0x7F,0x77,0x7F,0x3E,}; const uint8_t* const _I_Lock_7x8[] = {_I_Lock_7x8_0}; @@ -645,9 +627,6 @@ const uint8_t* const _I_Error_62x31[] = {_I_Error_62x31_0}; const uint8_t _I_Updating_32x40_0[] = {0x01,0x00,0x56,0x00,0xc0,0x7f,0xc0,0x03,0xc0,0x01,0x97,0x82,0x07,0x00,0xe0,0x5c,0x00,0x65,0x38,0x01,0x94,0x70,0x06,0x50,0xe0,0x19,0x41,0xc0,0x65,0xff,0x01,0xb4,0x0c,0x02,0x7e,0x08,0x38,0x0c,0x7c,0xd6,0x70,0x18,0xfb,0xfe,0xfc,0x0c,0x18,0xc8,0x78,0x20,0x33,0x81,0x8f,0x8a,0x07,0x3e,0xbe,0x70,0x38,0x71,0xff,0xc7,0x0f,0xc7,0x0f,0xf8,0x71,0xc0,0x76,0x13,0x30,0xd9,0x88,0xcc,0x5f,0x03,0xb2,0x21,0xa1,0x2c,0xc0,0x26,0x82,0x10,0x1f,0x80,0xd1,0x24,0x40,0x04,}; const uint8_t* const _I_Updating_32x40[] = {_I_Updating_32x40_0}; -const uint8_t _I_DolphinExcited_64x63_0[] = {0x01,0x00,0x36,0x01,0x00,0x25,0x00,0x0f,0xd2,0x00,0x3b,0xe0,0x00,0xeb,0x10,0x0c,0x34,0x40,0x30,0xd0,0x88,0x80,0x1d,0xa1,0x00,0x42,0xfc,0x7f,0xc0,0x63,0x04,0x01,0x0e,0x02,0x0f,0x00,0x00,0x8c,0x08,0x0e,0x37,0x00,0x10,0xc6,0x20,0x10,0x10,0xd9,0x11,0x92,0x1c,0x1a,0x3e,0x00,0x04,0x42,0x02,0x1a,0x20,0xb0,0xce,0x00,0x64,0x07,0x20,0x59,0x16,0x50,0x36,0x45,0x94,0x84,0x78,0x20,0x60,0x75,0x8e,0x43,0x06,0x63,0x3c,0x33,0x94,0x0c,0xd2,0x5c,0x30,0x38,0xe4,0x08,0x43,0x10,0xc0,0x5e,0x06,0x22,0x53,0x1a,0x02,0x08,0x7f,0xd0,0x32,0xc1,0x50,0x21,0x14,0x0e,0x70,0x1c,0x46,0xe2,0x07,0x19,0x06,0x3c,0xdc,0x20,0x91,0xae,0x01,0xcc,0xbe,0x30,0x09,0xfc,0x12,0x41,0xff,0x83,0xcc,0x0a,0xa3,0x1f,0x03,0x99,0xe8,0x7c,0x10,0xf8,0x25,0xa0,0x5e,0x50,0x0f,0x84,0x1e,0x09,0x54,0x03,0x9f,0xf2,0x07,0x02,0xd5,0x11,0xca,0x01,0xfe,0x80,0xc0,0xaa,0x9f,0xf0,0x39,0x5f,0xd0,0x43,0xaa,0x83,0x41,0x92,0xc3,0x1f,0x03,0x8d,0x52,0x02,0x2e,0x25,0xc9,0x6a,0x99,0x46,0xa6,0x2a,0xa0,0x1c,0xaf,0xca,0x62,0x94,0x28,0xcb,0x7e,0x0f,0x15,0x71,0xf8,0x3c,0x22,0x71,0x03,0x8a,0x84,0x67,0x18,0x0f,0xac,0x1c,0x0e,0x38,0x08,0x0c,0x3e,0x01,0xae,0xbd,0x13,0x0c,0x0e,0x35,0x8e,0xa8,0x1c,0xb0,0x1f,0xf8,0x06,0x83,0xf4,0x27,0x38,0x07,0xff,0xff,0x8f,0x03,0xa0,0x4c,0x80,0xed,0x60,0x03,0xb4,0x60,0x0e,0xd0,0x60,0x3a,0x87,0x84,0x0e,0xb7,0xc2,0xfa,0x18,0x05,0x44,0x20,0x73,0xff,0xf7,0xce,0xe4,0x07,0x2d,0x52,0x2c,0x80,0xe7,0x54,0xea,0x81,0xd7,0x50,0x0f,0x7a,0xaa,0x3d,0x41,0xe2,0x07,0x5a,0x80,0x3c,0xa0,0x40,0x72,0xd0,0x6a,0x80,0xa2,0x07,0x3a,0x05,0x54,0x8e,0x20,0x73,0xc0,0x03,0xd8,0x60,0x30,0x40,0x3a,0xc0,0x00,0xee,0xea,0x10,0x3b,0x80,}; -const uint8_t* const _I_DolphinExcited_64x63[] = {_I_DolphinExcited_64x63_0}; - const uint8_t _I_DolphinMafia_115x62_0[] = {0x01,0x00,0x21,0x02,0x00,0x1e,0x02,0x06,0x0e,0xcb,0x04,0x10,0x1d,0x91,0x88,0x40,0x3b,0x20,0xc0,0xec,0xc0,0x40,0x62,0x03,0xac,0x80,0x03,0xb2,0x31,0x00,0x90,0x03,0xae,0x5e,0x0e,0xcf,0xc4,0x56,0x01,0x40,0x07,0x56,0xbe,0x14,0x0e,0x2f,0xf1,0x5e,0x2a,0xa1,0xd1,0xc0,0x7c,0x3f,0xf0,0x70,0x73,0x70,0x35,0x41,0xd1,0xc0,0x7f,0xff,0xf0,0xf0,0x73,0x50,0x03,0xa4,0x0d,0x10,0x74,0x07,0x46,0x55,0xe0,0x07,0x10,0xb1,0xc3,0xa3,0x55,0xfe,0x03,0x88,0x94,0xe1,0xd1,0xd5,0x03,0x4a,0x3e,0x59,0x9e,0xaf,0xfe,0xff,0x05,0x60,0x4e,0xab,0xf5,0xff,0x95,0xb4,0xa4,0x3a,0x3f,0xd0,0xe0,0xfa,0x20,0x20,0xf8,0xd5,0xff,0xb5,0xf0,0x0f,0x88,0x3a,0x6a,0xbf,0xf8,0xaf,0x82,0x6f,0x03,0x07,0x47,0xaf,0xff,0x0a,0xfe,0x5f,0xc1,0xd3,0xf6,0xbf,0xe0,0x7f,0xfe,0xf0,0x73,0x41,0x00,0x43,0xfa,0xd7,0xf8,0x27,0xfe,0xe0,0x73,0x40,0x80,0x43,0xfe,0xab,0xfe,0x21,0xfc,0xe5,0x9b,0x05,0x48,0xea,0x3f,0xc8,0xfa,0xc4,0x66,0x07,0x44,0x0e,0x8f,0x00,0xb0,0x2b,0x31,0x07,0x0f,0x00,0x1c,0x72,0x00,0x70,0xf8,0x37,0xe5,0x81,0xff,0x89,0x08,0xf2,0x71,0x80,0x20,0xfe,0x2b,0xf0,0x5f,0xc0,0x38,0xc8,0xa5,0x60,0xc3,0x00,0xc7,0xf9,0xaf,0x81,0x2d,0x04,0x34,0x40,0xe1,0x98,0x47,0x68,0x04,0x92,0xab,0xc0,0x7e,0xb7,0xf7,0x39,0x03,0x85,0x8e,0x24,0xf1,0xc0,0x7f,0xf5,0x78,0x0f,0x53,0xb4,0xbc,0x1f,0xb8,0x1a,0x0c,0x61,0xc5,0x82,0xab,0xc0,0x3e,0xa3,0xa2,0xfc,0x07,0x46,0x09,0x60,0x19,0x8f,0x80,0xec,0x38,0x08,0x52,0x6c,0xb8,0xdc,0x28,0x7c,0x10,0x2a,0x5f,0x0f,0xfc,0x5a,0x01,0x05,0x1a,0x8e,0x02,0x02,0x1d,0x1f,0x81,0xa8,0xbe,0x13,0xf8,0x52,0x2c,0x8c,0x62,0x77,0x42,0x11,0x40,0xe0,0xca,0x93,0x8e,0x03,0x8a,0x30,0x10,0x48,0x54,0x03,0x04,0xbb,0x2c,0x00,0x0c,0x64,0x80,0xe4,0x0e,0x88,0x38,0x7c,0x10,0x04,0x09,0x48,0x83,0xac,0x1b,0x18,0xf3,0x44,0xc1,0xca,0x1d,0x15,0x40,0x8e,0x05,0x02,0x20,0xe6,0x24,0x12,0x8c,0x8b,0x05,0x21,0x07,0x24,0x14,0x08,0x73,0x80,0x19,0x78,0x43,0xb2,0xff,0x15,0x30,0xc4,0x01,0x26,0x8f,0x14,0x61,0xa9,0x8a,0x09,0x10,0x02,0x12,0x1c,0x80,0x84,0xaf,0x10,0x71,0xaa,0xc4,0x00,0x3b,0x04,0xea,0x24,0x48,0x1c,0xbd,0x8f,0xf8,0x00,0x67,0xf0,0x09,0x40,0x20,0x61,0x00,0xe4,0xf6,0x07,0x4b,0xc1,0x1f,0x07,0x14,0x40,0x1c,0x9d,0x66,0x79,0x24,0xc6,0xa0,0x0e,0x32,0x51,0xfa,0xce,0xe7,0x50,0x07,0x1c,0x80,0x30,0x58,0x0e,0xa2,0xcc,0xa0,0x19,0x00,0x71,0x42,0x13,0x27,0x40,0xf5,0x45,0x41,0xc5,0x08,0xb0,0x80,0xc6,0x18,0xf2,0x28,0x04,0x83,0xe8,0x58,0x10,0x30,0xc2,0x2c,0x40,0x91,0x89,0x3c,0x88,0x62,0x21,0xd2,0xff,0x03,0x87,0xc8,0x12,0x19,0x08,0x39,0x3e,0x83,0xb2,0x4a,0x0e,0xa2,0x0d,0xc0,0xe0,0x50,0x06,0xa7,0xe8,0x2c,0x94,0xc2,0x09,0x50,0x8c,0xce,0x20,0x34,0x70,0x71,0x41,0x3e,0x85,0xe2,0xe0,0x41,0x38,0x1e,0x28,0x3c,0x19,0xc8,0x70,0x4f,0xc1,0xdc,0xe0,0x74,0x01,0xd8,0xc6,0x24,0x00,0x82,0x81,0x7c,0x12,0xa6,0x7e,0x10,0x28,0xd8,0x22,0x00,0xe3,0xfc,0x34,0x53,0x00,0x23,0x1c,0x04,0x44,0x0e,0x50,0x10,0xeb,0x17,0xca,0x1c,0x07,0x20,}; const uint8_t* const _I_DolphinMafia_115x62[] = {_I_DolphinMafia_115x62_0}; @@ -657,9 +636,6 @@ const uint8_t* const _I_DolphinNice_96x59[] = {_I_DolphinNice_96x59_0}; const uint8_t _I_DolphinWait_61x59_0[] = {0x01,0x00,0x56,0x01,0x00,0x17,0xfa,0x1e,0x06,0x4f,0x84,0x06,0xe0,0x07,0x48,0x64,0x03,0x01,0x01,0x03,0x9c,0x0c,0x04,0x30,0x60,0x31,0x70,0x00,0x65,0x08,0x01,0x94,0xc0,0x06,0x51,0x00,0x5b,0x48,0x00,0x65,0x04,0x01,0x95,0x00,0x82,0xd8,0x00,0x19,0x40,0x7e,0x00,0x75,0x1f,0x88,0xe0,0x88,0x02,0x1a,0x1f,0x94,0x14,0x0e,0xbf,0x98,0x58,0x5c,0x42,0x45,0x00,0x9e,0x99,0x87,0x01,0x02,0x11,0x94,0xf2,0x2e,0x03,0x18,0x39,0x28,0x70,0x1f,0xc0,0x3e,0x42,0x00,0xe5,0x80,0xff,0xdf,0xc0,0xe5,0xf8,0x85,0xd8,0x10,0x27,0x40,0xf9,0xc2,0x63,0x88,0x12,0x82,0x6a,0x20,0x50,0x41,0xe9,0x42,0x20,0x95,0x48,0x6e,0x0c,0xfa,0x9a,0xaf,0xf9,0x90,0xe2,0x10,0x2e,0xac,0xe0,0x0e,0x98,0x29,0x52,0x11,0x13,0x23,0x15,0x3e,0x20,0x3c,0x61,0x40,0x52,0xfc,0x4f,0xe2,0x10,0x38,0x68,0x1c,0xa0,0xfc,0x08,0xbe,0x04,0x1e,0x5e,0x01,0xb9,0x03,0xc5,0x60,0x24,0xf2,0x84,0x60,0x63,0x40,0x71,0x27,0x9c,0x0e,0x2b,0x04,0x6c,0xa4,0x06,0x15,0x08,0x6c,0x99,0x8c,0xa6,0x0f,0x81,0x00,0x0c,0x08,0xf0,0x3c,0x05,0x61,0xc0,0x40,0x86,0xd0,0x30,0x78,0x80,0x0c,0xc6,0x2b,0x92,0x00,0x0d,0x51,0xf0,0x2d,0x42,0x0a,0x8e,0xaa,0x34,0x0f,0x4a,0x85,0x55,0x6e,0x20,0xf3,0xd5,0x6a,0x84,0xa2,0x66,0x2a,0x05,0xf7,0xaa,0x07,0x18,0xaf,0xfb,0x7f,0xea,0xc1,0xef,0xc0,0xe3,0xea,0x80,0xf8,0x27,0xf0,0x0a,0xc0,0x1c,0x67,0xa2,0xd1,0xb1,0xc0,0x34,0x00,0x71,0x14,0x8f,0x00,0x98,0x34,0x02,0x69,0xd0,0x37,0x90,0x16,0xf1,0x00,0x06,0xe1,0x84,0x31,0x89,0x14,0xe9,0xdc,0x40,0x38,0xa4,0xc4,0x4c,0x3c,0x1f,0x88,0x8c,0x5b,0xc3,0x01,0xbc,0x40,0x3f,0xf0,0xf6,0x71,0x0c,0x0b,0xe0,0x07,0x3c,0x0a,0xf8,0xa3,0xf0,0x03,0xb8,0xd8,0x80,0xe8,0x87,0x1b,0xa8,0x1c,0x78,0x1f,0xf8,0x0e,0x7e,0x01,0x6a,0x03,0x94,0x0f,0xfd,0xa0,0x80,0x7d,0x49,0x04,0x4d,0x12,0xc0,0xfa,0x83,0x83,0xbe,0x26,0x8d,0x02,0x05,0xd5,0xff,0xff,0xeb,0xe9,0x31,0x90,0x40,0x80,}; const uint8_t* const _I_DolphinWait_61x59[] = {_I_DolphinWait_61x59_0}; -const uint8_t _I_iButtonDolphinSuccess_109x60_0[] = {0x01,0x00,0xac,0x01,0x00,0x17,0xfe,0x1e,0x0c,0xaf,0x04,0x02,0xe0,0x0d,0xa8,0xf4,0x03,0x01,0x03,0x06,0x46,0x02,0x02,0x03,0x18,0xe0,0x36,0x2c,0x00,0x36,0x00,0x2c,0x40,0x3e,0x60,0xd8,0x84,0x01,0x0c,0x5a,0x40,0x05,0x82,0x01,0x0e,0x04,0x0d,0x70,0x42,0x04,0x90,0x49,0x02,0xe4,0x20,0x41,0x28,0xc0,0x07,0x40,0x06,0xf8,0x00,0xa4,0x00,0xd6,0x03,0xa8,0x37,0x44,0x2a,0x31,0x74,0xd3,0x83,0x57,0x80,0x0d,0xc7,0x18,0xa9,0xa8,0x36,0x2a,0x86,0x06,0x8d,0xfc,0x36,0x60,0xd7,0xc0,0x3b,0x8c,0x36,0xf0,0x4a,0x05,0xf9,0x6e,0x5e,0x06,0x23,0x41,0x24,0x1f,0xf6,0x01,0x74,0x01,0xb1,0xe3,0x82,0x81,0x47,0x40,0x0d,0x7c,0x87,0x8e,0x12,0x05,0x1a,0x84,0x0d,0xb6,0xa0,0xd2,0x85,0x86,0xc8,0x1a,0x50,0x40,0x69,0x40,0xb2,0x1f,0xf0,0x69,0x50,0x01,0xa5,0x08,0xfc,0x03,0x5f,0x60,0x0d,0x28,0x84,0x1a,0x07,0x18,0x06,0xaf,0x00,0x1a,0x3c,0x03,0xb8,0xc3,0x20,0xd0,0x28,0x87,0xfc,0x8a,0x50,0x08,0x78,0x08,0x70,0x77,0x0c,0x44,0x06,0x05,0x30,0xff,0x18,0x4a,0x01,0x30,0x01,0x0d,0x33,0x19,0x11,0x1b,0x8c,0xa2,0xf8,0x7d,0x27,0x71,0xd0,0x20,0x51,0x20,0x68,0xd5,0x00,0x42,0x0d,0x2c,0x00,0x08,0x64,0x10,0x19,0x20,0x28,0x75,0x07,0x53,0x3d,0x18,0x35,0x2a,0x9f,0xf4,0x9a,0x41,0x90,0x23,0x00,0x94,0x43,0xe0,0x5e,0xae,0x03,0x9d,0xb4,0xe0,0xd1,0x0d,0x8c,0xd0,0x52,0xb1,0x00,0xd9,0x83,0x46,0x34,0x45,0x41,0xa8,0x9f,0x86,0x01,0x14,0x05,0x08,0x08,0x81,0xa6,0x62,0x10,0x68,0xe5,0x20,0x70,0x41,0x80,0x80,0x10,0xc4,0x34,0x48,0x04,0x2a,0x38,0x0d,0x99,0x16,0x02,0x1a,0xd5,0x10,0x6c,0x5e,0x2e,0x0b,0xa1,0x4b,0x0a,0x60,0xc1,0xa7,0x84,0xfc,0x58,0x01,0xb5,0x02,0x82,0xb4,0xc4,0x16,0x22,0xa5,0x06,0x96,0x19,0x20,0x20,0xd7,0x30,0x8c,0x0f,0x08,0x05,0x10,0x68,0xa1,0x44,0x1a,0x98,0x08,0x14,0x11,0x28,0x21,0x91,0x1d,0x8f,0x83,0xfe,0x07,0x1b,0x00,0x34,0x61,0x00,0xd3,0x1d,0x8c,0x7a,0x01,0x7e,0x80,0x56,0x30,0x06,0xb1,0x4a,0x08,0xd4,0xbf,0xc1,0x31,0xc0,0x7f,0xe8,0xf0,0x08,0x3c,0x40,0x1a,0x80,0x04,0x5a,0x8c,0x10,0x80,0x40,0xd7,0x05,0x08,0x36,0xc0,0xe2,0x0d,0xb8,0x30,0x34,0x45,0x82,0x0d,0x72,0x49,0x03,0x5a,0x41,0x55,0xf8,0x7f,0xff,0xe8,0x72,0x06,0xae,0x03,0xf4,0x0c,0x1d,0xf8,0x18,0x60,0x40,0xd2,0x4b,0x9f,0xd0,0x1a,0x35,0x71,0x48,0xc0,0x95,0x42,0x0d,0x4d,0x50,0x70,0x75,0x40,0xd1,0x80,0x83,0x5a,0xa1,0x55,0x00,0x0c,0x05,0xa4,0x20,0xd2,}; -const uint8_t* const _I_iButtonDolphinSuccess_109x60[] = {_I_iButtonDolphinSuccess_109x60_0}; - const uint8_t _I_iButtonDolphinVerySuccess_108x52_0[] = {0x01,0x00,0xc2,0x01,0x00,0x0f,0xe2,0xfe,0x0d,0xb8,0x3e,0x02,0x06,0x0c,0x9f,0x00,0x08,0x61,0x80,0xd9,0x8c,0x00,0x86,0x60,0x0d,0x98,0x30,0x08,0x6a,0x00,0xd9,0x80,0x80,0x87,0x40,0x0c,0x8c,0x00,0x0c,0xa8,0x01,0x12,0x00,0x2d,0x00,0x22,0x70,0x20,0x6b,0xc8,0x02,0x26,0x62,0x88,0x80,0x6c,0xc9,0x24,0x0d,0x9a,0x07,0x17,0xfe,0x1d,0x68,0x40,0x6c,0xe7,0x48,0x04,0x28,0x10,0x34,0xe8,0x10,0xd1,0x11,0xc4,0x01,0xa5,0x04,0x06,0x96,0xa0,0xa6,0x24,0xc2,0x88,0x17,0x88,0x1a,0x7d,0x43,0x78,0x82,0x4a,0x40,0x03,0x20,0xb0,0xff,0x20,0x16,0xa3,0xb2,0x48,0x03,0xe4,0x0d,0x1f,0xfc,0x06,0x3a,0x0d,0x4a,0x00,0x34,0xf8,0x00,0xd1,0x37,0x0f,0x82,0x9e,0x95,0x58,0x17,0x83,0xff,0x81,0x1b,0x0f,0xf1,0xfe,0x71,0xe0,0x69,0x7c,0x3f,0xe0,0x82,0xff,0xcf,0xc0,0x85,0x61,0x80,0x43,0xb0,0x5f,0xa8,0x79,0xdc,0x81,0xa5,0x70,0xc0,0x68,0x3c,0x10,0x1a,0x17,0xd5,0x28,0x42,0xd1,0x8f,0x84,0x46,0x83,0xb0,0x8e,0x40,0x34,0x5f,0xa8,0x38,0x34,0x45,0xa2,0x0d,0x18,0x04,0x9b,0x50,0x03,0x1a,0x14,0x35,0x36,0x5f,0x8f,0xf8,0xb8,0xa4,0x19,0x40,0x18,0xe8,0xa0,0xca,0x22,0xfe,0x7f,0xc4,0x05,0x20,0xa5,0x80,0xc6,0x82,0xcb,0x3f,0xf3,0x44,0xfc,0x12,0x40,0x18,0xe8,0x51,0x82,0x52,0x28,0xfc,0x38,0x0a,0x3e,0x48,0x98,0x6c,0x8f,0x43,0x00,0xe0,0x63,0xe0,0x62,0xe2,0x91,0x90,0x0a,0x02,0x0d,0x2f,0x82,0x50,0x41,0xa3,0x80,0x90,0x41,0x04,0xc3,0x01,0xc0,0x83,0x46,0x71,0x30,0x06,0x95,0x82,0x21,0x02,0x6e,0x88,0x6c,0x43,0x83,0x1f,0x2f,0x88,0x34,0x62,0x00,0xd1,0x15,0x08,0x2c,0x60,0xcc,0x51,0x0f,0x08,0xcc,0x81,0xa2,0x12,0x10,0x68,0xc6,0x3f,0x06,0xc2,0x06,0x8e,0x02,0x16,0x41,0x20,0x10,0xf8,0x01,0x85,0x00,0x19,0x0d,0x82,0x18,0x07,0x20,0x81,0x00,0x0c,0x9c,0x31,0x08,0x42,0x74,0x81,0xab,0x80,0x03,0x0c,0x32,0x11,0x0b,0x06,0xb9,0xc0,0x43,0xa3,0x10,0x8b,0x83,0x5c,0xe0,0x20,0x81,0xc8,0x26,0x49,0x4c,0x40,0x02,0x86,0x0a,0xc5,0x22,0x32,0x50,0x6b,0x93,0x86,0xc0,0x0d,0x19,0x18,0x35,0x8c,0x84,0x79,0x1a,0x84,0x84,0x1a,0xdf,0xc2,0xe0,0x8a,0xc7,0x51,0x22,0x06,0xb5,0x5e,0x3f,0x00,0x77,0x0d,0x60,0x36,0xfa,0xa9,0xd7,0x00,0x08,0x3a,0xc9,0x02,0x48,0xc0,0x05,0x54,0xba,0x98,0x8a,0xa8,0xf1,0x20,0x6a,0x6a,0x3d,0x43,0x61,0x80,0x4a,0x81,0xaf,0x40,0xea,0x8d,0x86,0x01,0x56,0x06,0x93,0x60,0x80,0x05,0xea,0x01,0x94,0xac,0x1b,0x11,0x80,0x19,0x45,0x41,0x44,0x0d,0x58,0x33,0x18,0xa1,0x4f,0xf3,0x06,0x1f,0x01,0x76,0x58,0x00,0xd9,0x83,0x52,0x7c,0x11,0x38,0x51,0x40,0x80,}; const uint8_t* const _I_iButtonDolphinVerySuccess_108x52[] = {_I_iButtonDolphinVerySuccess_108x52_0}; @@ -720,16 +696,13 @@ const Icon I_DolphinFirstStart6_58x54 = {.width=58,.height=54,.frame_count=1,.fr const Icon I_DolphinFirstStart7_61x51 = {.width=61,.height=51,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart7_61x51}; const Icon I_DolphinFirstStart8_56x51 = {.width=56,.height=51,.frame_count=1,.frame_rate=0,.frames=_I_DolphinFirstStart8_56x51}; const Icon I_DolphinOkay_41x43 = {.width=41,.height=43,.frame_count=1,.frame_rate=0,.frames=_I_DolphinOkay_41x43}; -const Icon I_Flipper_young_80x60 = {.width=80,.height=60,.frame_count=1,.frame_rate=0,.frames=_I_Flipper_young_80x60}; const Icon I_ArrowDownEmpty_14x15 = {.width=14,.height=15,.frame_count=1,.frame_rate=0,.frames=_I_ArrowDownEmpty_14x15}; const Icon I_ArrowDownFilled_14x15 = {.width=14,.height=15,.frame_count=1,.frame_rate=0,.frames=_I_ArrowDownFilled_14x15}; const Icon I_ArrowUpEmpty_14x15 = {.width=14,.height=15,.frame_count=1,.frame_rate=0,.frames=_I_ArrowUpEmpty_14x15}; const Icon I_ArrowUpFilled_14x15 = {.width=14,.height=15,.frame_count=1,.frame_rate=0,.frames=_I_ArrowUpFilled_14x15}; -const Icon I_Back_15x10 = {.width=15,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_Back_15x10}; const Icon I_DolphinReadingSuccess_59x63 = {.width=59,.height=63,.frame_count=1,.frame_rate=0,.frames=_I_DolphinReadingSuccess_59x63}; const Icon I_Down_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Down_25x27}; const Icon I_Down_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Down_hvr_25x27}; -const Icon I_Fill_marker_7x7 = {.width=7,.height=7,.frame_count=1,.frame_rate=0,.frames=_I_Fill_marker_7x7}; const Icon I_InfraredArrowDown_4x8 = {.width=8,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_InfraredArrowDown_4x8}; const Icon I_InfraredArrowUp_4x8 = {.width=8,.height=4,.frame_count=1,.frame_rate=0,.frames=_I_InfraredArrowUp_4x8}; const Icon I_InfraredLearnShort_128x31 = {.width=128,.height=31,.frame_count=1,.frame_rate=0,.frames=_I_InfraredLearnShort_128x31}; @@ -746,7 +719,6 @@ const Icon I_Vol_down_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0 const Icon I_Vol_down_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Vol_down_hvr_25x27}; const Icon I_Vol_up_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Vol_up_25x27}; const Icon I_Vol_up_hvr_25x27 = {.width=25,.height=27,.frame_count=1,.frame_rate=0,.frames=_I_Vol_up_hvr_25x27}; -const Icon I_Back3_45x8 = {.width=45,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Back3_45x8}; const Icon I_DoorLeft_70x55 = {.width=70,.height=55,.frame_count=1,.frame_rate=0,.frames=_I_DoorLeft_70x55}; const Icon I_DoorLocked_10x56 = {.width=10,.height=56,.frame_count=1,.frame_rate=0,.frames=_I_DoorLocked_10x56}; const Icon I_DoorRight_70x55 = {.width=70,.height=55,.frame_count=1,.frame_rate=0,.frames=_I_DoorRight_70x55}; @@ -810,7 +782,6 @@ const Icon I_RFIDBigChip_37x36 = {.width=37,.height=36,.frame_count=1,.frame_rat const Icon I_RFIDDolphinReceive_97x61 = {.width=97,.height=61,.frame_count=1,.frame_rate=0,.frames=_I_RFIDDolphinReceive_97x61}; const Icon I_RFIDDolphinSend_97x61 = {.width=97,.height=61,.frame_count=1,.frame_rate=0,.frames=_I_RFIDDolphinSend_97x61}; const Icon I_RFIDDolphinSuccess_108x57 = {.width=108,.height=57,.frame_count=1,.frame_rate=0,.frames=_I_RFIDDolphinSuccess_108x57}; -const Icon I_SDError_43x35 = {.width=43,.height=35,.frame_count=1,.frame_rate=0,.frames=_I_SDError_43x35}; const Icon I_SDQuestion_35x43 = {.width=35,.height=43,.frame_count=1,.frame_rate=0,.frames=_I_SDQuestion_35x43}; const Icon I_Cry_dolph_55x52 = {.width=55,.height=52,.frame_count=1,.frame_rate=0,.frames=_I_Cry_dolph_55x52}; const Icon I_Attention_5x8 = {.width=5,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Attention_5x8}; @@ -827,7 +798,6 @@ const Icon I_PlaceholderL_11x13 = {.width=11,.height=13,.frame_count=1,.frame_ra const Icon I_PlaceholderR_30x13 = {.width=30,.height=13,.frame_count=1,.frame_rate=0,.frames=_I_PlaceholderR_30x13}; const Icon I_SDcardFail_11x8 = {.width=11,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_SDcardFail_11x8}; const Icon I_SDcardMounted_11x8 = {.width=11,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_SDcardMounted_11x8}; -const Icon I_USBConnected_15x8 = {.width=15,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_USBConnected_15x8}; const Icon I_Lock_7x8 = {.width=7,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Lock_7x8}; const Icon I_MHz_25x11 = {.width=25,.height=11,.frame_count=1,.frame_rate=0,.frames=_I_MHz_25x11}; const Icon I_Quest_7x8 = {.width=7,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Quest_7x8}; @@ -839,11 +809,9 @@ const Icon I_Connected_62x31 = {.width=62,.height=31,.frame_count=1,.frame_rate= const Icon I_Drive_112x35 = {.width=112,.height=35,.frame_count=1,.frame_rate=0,.frames=_I_Drive_112x35}; const Icon I_Error_62x31 = {.width=62,.height=31,.frame_count=1,.frame_rate=0,.frames=_I_Error_62x31}; const Icon I_Updating_32x40 = {.width=32,.height=40,.frame_count=1,.frame_rate=0,.frames=_I_Updating_32x40}; -const Icon I_DolphinExcited_64x63 = {.width=64,.height=63,.frame_count=1,.frame_rate=0,.frames=_I_DolphinExcited_64x63}; const Icon I_DolphinMafia_115x62 = {.width=115,.height=62,.frame_count=1,.frame_rate=0,.frames=_I_DolphinMafia_115x62}; const Icon I_DolphinNice_96x59 = {.width=96,.height=59,.frame_count=1,.frame_rate=0,.frames=_I_DolphinNice_96x59}; const Icon I_DolphinWait_61x59 = {.width=61,.height=59,.frame_count=1,.frame_rate=0,.frames=_I_DolphinWait_61x59}; -const Icon I_iButtonDolphinSuccess_109x60 = {.width=109,.height=60,.frame_count=1,.frame_rate=0,.frames=_I_iButtonDolphinSuccess_109x60}; const Icon I_iButtonDolphinVerySuccess_108x52 = {.width=108,.height=52,.frame_count=1,.frame_rate=0,.frames=_I_iButtonDolphinVerySuccess_108x52}; const Icon I_iButtonKey_49x44 = {.width=49,.height=44,.frame_count=1,.frame_rate=0,.frames=_I_iButtonKey_49x44}; diff --git a/assets/compiled/assets_icons.h b/assets/compiled/assets_icons.h index 12ddb78e..64cd3d36 100644 --- a/assets/compiled/assets_icons.h +++ b/assets/compiled/assets_icons.h @@ -55,16 +55,13 @@ extern const Icon I_DolphinFirstStart6_58x54; extern const Icon I_DolphinFirstStart7_61x51; extern const Icon I_DolphinFirstStart8_56x51; extern const Icon I_DolphinOkay_41x43; -extern const Icon I_Flipper_young_80x60; extern const Icon I_ArrowDownEmpty_14x15; extern const Icon I_ArrowDownFilled_14x15; extern const Icon I_ArrowUpEmpty_14x15; extern const Icon I_ArrowUpFilled_14x15; -extern const Icon I_Back_15x10; extern const Icon I_DolphinReadingSuccess_59x63; extern const Icon I_Down_25x27; extern const Icon I_Down_hvr_25x27; -extern const Icon I_Fill_marker_7x7; extern const Icon I_InfraredArrowDown_4x8; extern const Icon I_InfraredArrowUp_4x8; extern const Icon I_InfraredLearnShort_128x31; @@ -81,7 +78,6 @@ extern const Icon I_Vol_down_25x27; extern const Icon I_Vol_down_hvr_25x27; extern const Icon I_Vol_up_25x27; extern const Icon I_Vol_up_hvr_25x27; -extern const Icon I_Back3_45x8; extern const Icon I_DoorLeft_70x55; extern const Icon I_DoorLocked_10x56; extern const Icon I_DoorRight_70x55; @@ -145,7 +141,6 @@ extern const Icon I_RFIDBigChip_37x36; extern const Icon I_RFIDDolphinReceive_97x61; extern const Icon I_RFIDDolphinSend_97x61; extern const Icon I_RFIDDolphinSuccess_108x57; -extern const Icon I_SDError_43x35; extern const Icon I_SDQuestion_35x43; extern const Icon I_Cry_dolph_55x52; extern const Icon I_Attention_5x8; @@ -162,7 +157,6 @@ extern const Icon I_PlaceholderL_11x13; extern const Icon I_PlaceholderR_30x13; extern const Icon I_SDcardFail_11x8; extern const Icon I_SDcardMounted_11x8; -extern const Icon I_USBConnected_15x8; extern const Icon I_Lock_7x8; extern const Icon I_MHz_25x11; extern const Icon I_Quest_7x8; @@ -174,10 +168,8 @@ extern const Icon I_Connected_62x31; extern const Icon I_Drive_112x35; extern const Icon I_Error_62x31; extern const Icon I_Updating_32x40; -extern const Icon I_DolphinExcited_64x63; extern const Icon I_DolphinMafia_115x62; extern const Icon I_DolphinNice_96x59; extern const Icon I_DolphinWait_61x59; -extern const Icon I_iButtonDolphinSuccess_109x60; extern const Icon I_iButtonDolphinVerySuccess_108x52; extern const Icon I_iButtonKey_49x44; diff --git a/assets/icons/Dolphin/Flipper_young_80x60.png b/assets/icons/Dolphin/Flipper_young_80x60.png deleted file mode 100644 index 4725aab90846789e5a613fec49a1288c304a526f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 643 zcmV-}0(||6P)y0EK41}v%o5clj`G;{qY#enzz>N*Y%{-8Hr0u~UWsGflC>#xD4>MaH0O`yL-B!$) z&p-y05g~vO7i0}2fZHl1Vn$x2Isp*WETqOCt_pw@_qPT@0tvJah#W))7fyj)fys~$ z#BkOV&!Q|dH%kk3+eJCd39wDNRmh(s3ZN`tr@(L${lhMeq73wNNK4k0&!3z43}l{S z=6!Q^gyjiX;iAkg@%oH6cEco=K{>%0g=)?}q~2k1)5w?#$TQj?0#i`t<94WT5Ge;7 zS$dbNSBBXBeg@A8Yp3f4fx~NR;Z1sz5wy!a6%|}on8rQNP2_#>#L;fddQzz^FO(iv zx<-*m3kox@m-6R{&^4Co9{BAMA9xVwjgp%>Z*%50)l0P_c*qs-6B*W*yg<(brxxX0 z4n#Lc6Y{<@xWzs5LPkWflglQk=*y1y;CEXI>uH^;jTWoa?_$g?K)&yUm6Cf=23+c) zb_RAUS?ATjJ#o4h=@eKHA<5zu(~@v? zvkcoEpM}_2Douk(lSZsWOu(mr3rHK+sxT%@S*g+#UKE~Q-Zvvr_$4A->glv3*StC$ z@4@50A46H>z_)<=cc-_3pD_2QWmgp*#{RY9^d03`;hTOR{3`Mq^C9E>PXb>7{{002 d{K3Cd;D3fJQb|_1G&%qP002ovPDHLkV1f?0B#Zz6 diff --git a/assets/icons/Infrared/Back_15x10.png b/assets/icons/Infrared/Back_15x10.png deleted file mode 100644 index fe9ac5aadfb8653053291fdca4c32010150ac3d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3600 zcmaJ^cT^MGw;q~Oq)0C!A(U$YlF)=mXrTn9h89$W5CQ~9F$s{MB8u2hiZrPrpoj)+ zD2Ox>1Pnz`5CNq~5dlL-sxRK(_5R);&o^t$oPEyuzHje+_MWw7E;~EgiV7Ro}P~6%*?ERSg)GMp4ez-c8Evsl+;Wb&kqKS(Xl4*UX=tVR6|R3G_MS5h#w>| z&aMv>Xas;+J|-iI{?KfL=K&eu1t{G*>XJ>vKVvGLH9Y}P5-+si=@+rcFGa^su=y=vq?HM^t)|#BoAG-)NAw>Z(~`L|hq+wg z40dQ@K1U~6skWq!NKT&7ol{BM6iSW|1y~oH>KvbKf65N6i~7#P8z+Y83J1)i7PPM_ ziX;INf%>}46#z)CT}?3G&jYlj7k&YNs;~0L?xx7uzZL`ltGu)O9$IYQ@lu*qCzkzE zxvEZ%7rV{Me203SiG=00(db>mx8&0-6x{0Iz5Guf@+s!+%p8^|dkHfVofwilc6N#) zx6M~Rw_d*Lf)Kw&v!E1K6@F=_UwkjPT~IC~o*#@w>0gg71WjI&b^$jwQww>EErXN1 zQEKzrRB7XD$IRCf`DrJ$!!%z0S-U*P1sPGuUAlB*32Klr0W zj15ckyXtP$T#e&@iAnh+qtwM(u8%qjt7;_b+KJeOp!!kTg|3CBT@r+Bd(EjtV$0`I z!N<`oP}&xwIAMz8AcA z34Zg49WgK58RB}eSV})EQ==_Q7RfHuF6e%nNeCpY1VJY0l|r9lc3Y>vP45c%x_xqD zGG;Mihv)^-m?o6p9)%EvlL|%hf(leUfpdWOVG6HXi-ypkV_AT!_C6n|< zG8LZcks6gcanH@2=5E%_yeHyz-(CFvagWn?Y}}tXe<>DvFMGecSlRtWv0-UDIs%>G z+~ML=P~)?CZVe~;_(H5r-UU>F8?OAd!ZlUd*Sh#QjAakigKtVJQ`htdu3ZXC~+-z$i?rX>mLPh87`Qf`e*+G>_4M7E+b_IV`XnwyK z_{D%BXBxmF@A6~mf5s1lm|*<~y8VR>g^i;%d)rb%G6O0*%ihsjTbs28SiP*4>3HMr z?2ue+&Bv<#jcc*Dt+N{s#TmvmrS+wqVhf}>q^+=Sc$j)HJ(fH^uK76x zAjiXxdmUe|-tF`xTQj!@N^I_LI673#KIscLMc~)kC;3cYoATa_ym_(aP7QbvQKJ^s z6O|FQeIs}M;2L`~;YaN-b6$4NQNWkyCC?a7^;yNUPxwOkRQNOaZ*MmcxF+yo8)S#6 zjO7j?Q7;v0GPzD}UTGDlHxQ_m*(vizFh}Ny(i*f?(|&i)&iD%*f`bxKhpvB!Oy7Sfvn(A zbSQ2*t0KDMUd36)#UIvl>5_CQb)lKdqYb?ch|6DSyc40BdfB6yZfSH}>DWO2bGh-! zc6M%riI;&_;whup8ze#)`7V#jD-M}lAJ6ef zT{il3@HBEQ=z^0?*lRr@C-i=1lEU@pY6}h?Rz06t9Uo78)HY7ZPRTAnixSex_J4qu z?k4MLJ-M)?7f6h}swK%1PO)6bH@*F3FP4#!@1gCc&^*FOnF4c!cVcjkL z@zADXe8khyr~A|rli1hy`B3?rnJLs%;S`Cr;#K?LP<60H_uC7zq9sam*T$55h=)GY zZ$$<##kZBUy=mh!z#7aPZGFb-pTA{TupF6l#+&W2S#rO;=A&j`Pwl6OlM7-o$}$H( zb5f?Op6+LOhE!XhULtjUKldp?(NC$ec0B4+g>8gWV{XF9+b6d#TXUv@>EzIX2eq}2 zg@|awYr9c!%2-x3xi4!bGvX=b$Ud=pi?Nq~4c<9*@{9A=TkWp~UbPKooGD)toI)}8 zcfo7JSlR`|JjAVP&ey92Im*YCZEMPZMsZ+YaA8db37)xmIeQRutE1ok= zA>L;UFjwq1GD5svysHC|9l2(5^zZiNJ}WH?pC=N$XR9cS;1$1qjq<}Kh*E@Y6lHT5 z_24LZ3ctdh@iZx=FFH&e?Bu*Vvld*}P5bcUgO2x2W;N&av{E@q^V6;hjmo7T;mb2} z5yIc?2fQN_7!NjT`Zm99Ulad!rflmGzK%Hb!?(`2owLRJAh^b9XjM+WSv)DmaycP1PzHdp69soEJLRa7{&TomNaIPHk;KR?aW1 zM;mMGR^9ibQnYmmkj_WW(*1z7lNmQ{P2MU5(3P5Q40nU{1ISJ0|%%B1&I$LpdzVw*GNY< zTx1B&*dJ_W3Nm59xdKEy9SdR*LrF9^0|EY97tXbREknSdzajJx1o%HeVH})6NHP@< zGSD&9#=-P;K?jU=V1`g*U41Q(E);eE0zCkM>1jh@aHt_%&lvR21?GCA`Uk+#RyP0m z;?5A@AUd4_hd?4CB6K43bjZ{|2+Y{n7y{LW=;~^75!y5+iH>DxlW1zc6|C?y9F;(! z6UZdcFGZ{$IgE|~b0hsv3Pj4kvLxC+$HYA_2m?!jz;vL$Qu+;aaQOeBMB=|_8Xb-Q zZ@mAfFwKog!9&n^8aa%L;}$MJ?UySG97)Av>13)KnH>826rF>}bTTc7OaXEKAoh3? zNdDvq+TOq64i0cT5{-@};qZ1=2rySbhd}U$AAngK>KPb7k;c}RFqpNa1=PyO$VgXL z-v(+8L+T-abFIj@Fe08r|IPLPFBkSl?ypWDQn;S2@Ki!L-rt5wCW8LX7*6FkQbu4%|qV>cQ5GRoH2MkU4=3}$I&FeAy5QnF>snov?hn$!ZrcoYD5&2VOB4%TL7AQGAAgA2q00IfgQF$9&eCT=*qh81)%?OS-9 z5f}-86&T#jLT&{@Hvse{Az|ajm{?vHQL+6w2{$NcN?(&}v8Upk&**)Z z8!UcyA(S~kzxipidOBx%tApMt7_nbiDS4{EAJ9j}7{L1E673NUEj1DBVu%q=kkC}? zYhSKL09fFl)558bjJDVw5&?FA)T4GtxM}oPbd`gWD*#FcM!>?;``D6@K(m@tvnbGf z0C?p1)SDMb1%N<$m?;>z&kxM~ur}lZ`bts;C4jzygC*QRJR6XC*y<3w@k_wdITESD zUjG`%XBdOkIP1&UBHW&9Sg}XcZ~?&ys3bvSPaq=GRHY37>9GT5vLfvqARCT+_3pZn z5)V>LC4vp8Z%u2@IMJ;sTAB7L%Dw8Rl1AD#(tPDZwZ2x6MUrh^Xv zq}ID$?dTaLO;1gXPWem+GJiA{>;xF@ zVB0483uLmnd`sGx@XSSxMY;5Co|F)NfN|Nr-tPI{XRU$t;osTV;{;IM!GLl2vg&oI zeaV23ua*XV4FFQ=))S4D*?{(pqHzGI{(9idy;KRCx7+|=o*#Mik;z`ZSE7u1ft*(| z)%6nW=so7fe23}{giQBLL>%P3eIVUL(y1QS$NB6LhgAOltWlxzS5Q6v=@H>Gk+UR; zJ)Q^hULUBw%)@EY%q@bJhsEyqO6UW3a7$z+aDveYts4_Nsa?#W4Adz(+`qG}1cN2C`7bV-%V z!Xus+6nCBY8bk#yTuZ=2bU_7tu75_KxqijM^D0V8sYx6&iPCmS)W*o5bc4mD>+{e| z?nCTx5h8QkXDc)fw<1$&%? zvScWImzR~#Q#|{ZfQUy5;*dbgjR`wmdBr3R>wVS%hyjFZkzW3%PWM1?4C{_5lHr8m8^Uf^|)6D$)C)-Q%K62lW zh21)3jn5BufH+<$5zz|DQf$u_H)}0YE$n%ph4aO&`9Wr=RXjt{vKATdGr9x5?wy&Q ziT)A7$A6hWx(VTRSTaEJykzl#3Q0(&&0Ve!8JW#a>Yp^EGktFg-XuDsj;ks+g*Qpw4fGA274@8J?AQD*`h^T~;gj zAB8It-4St1P)`fpnDt*7= zJFZQWF!W&%4|*}QzF-Ff4A5RUje(+uqQ;5ZBkidHSw2-=I_H*^XR(#AIi=4*eaXN8L>HX1&LeE`~02mlc;!4C~%AvnsRrUe`be zY1eGNY*=>d^LwS)r47N^_+im8(ZnO&M^+pO{m{9sIo`RfxjpmxtLRn5Rs3qFOxrysd~0(RO?MiL&=*NS6^x0+mzg8ZHc=ll3Il`f-@u>@x|AQ3AaL9 z(6QOfI8$2Si&oF1uZJ?{xKgkv{4W;hJr8nWLCy>+GbZY_8iUaJt^ z8=e`ycPnr6*hcGi;?KHK`jYsf9hWEDE4E3tnnYB^*~s?pPq^I4JuDPXyt{5Q)ApP@ zcEl<$wo(o!nJKY;LG;K`jr(ffPL?}T&r#P@dKnXLo4z#(i-{%IUmP3oe@+heW%vgo z12J>il@XQqDB&KFM zTJu5-+_c@2!t`Qp3THeqnv4dSIzSHCPZkqwWki-PWyjcC)E2}SoP1nSdEDT}RIW?f zs@~AC3uYUBm+dWs-fHsLBbDjNk~dx`EZe%6_kLk?em?hERX;T+HK!2Ck4rCC{-j=U zxWe8?SviG&fFC^G!F)eaj)#> zBg@LEF;_c}p0E`y=OQjd%C#5~`$G=c- zhx)H1w3oNPYv<5LYtNrNmtcPg%4F)h1fUdC7!=?e($OUbJm|q3>zWQtUZrJ&8hp)Fg-VDBJAI`j3vBEuzpeuL7 z>Vg=mh4_5xH>8XJxEvD}IGDq2rjKChbJmy!_n5@Wu6ssFd+y@8Y~%J0tkV z%t88^%~ocB`$_j2U$f3UBMItvn+lJ$mgO(giS7&4q#xiluK~r16Q%GnxK%i5dld2T zG;tQY);jNMP)7Y>JA15)`QhS*e|-<-)6Y+8?ziYQ%(rvW6$GWBgO!R^D?fu*=OsdT zzuOGDhbGb8KWi#|(vFg?6Iwt1oxwbi$8BT}oY(ds8EpJBX zEAD4~RZ3DVP*g{v4>bf$#sVhZL@zAJ8jtqHBC%+1`neXY0RV8wxV;xlCh4Vc21bk z0F1sj*vJrMK!dRa@K`DuM8gLXC@>lv{I@QQW$!LSz@Wb&)Brg6KS7~v9YAJ8G8Uw* zrlX31YH5IU_0^y{>iQa5Dj*GYs4hfZ7XsB(RfodVbzqwMpnonf%NyC-2Zl7a{Kpq- z1_%35sU#Q#5)u-k7NV&}B>O_3`uh41bq$DyhAIo8N}&^|Xqqa4qVQY6981BFaU?2^ zNC53BqP>VgR5+Lw>3>qdlm3+@Q2se4)`3B2Xc7dfroNlfZ=kL1{}09E|3y=%NbG;( z{Xc~%PIM9$g2YmYL1YZ8a6SsVu1GL5G8RoGlAVaez~86n;76nqDSku}i1i0?*cC_c zCWcUs{0+CYg;^6QR5SsDwKj)?SpsS}oHxuwT}wwtM_t`a-@+6MwJ_D!*VQ%G(J(jD z(}n6oH8p;7&54*GJeENH&Gr5-7y3u;ZYSVLEYIdxGAixs@{!=X4mX?3yLRi5-cI*1T>im0&)jYfAzg^3k{M+|f0;}1{tgd$aIk3!nBlbAh zI9ag9U%!6c`Fv}LWyx8an>f+>y*$0WJv{;8>!2vMhREb*zDs;gaN`IzfL#{YzxDxZ Q%z^^e7Ix-krk+v%1Ac@~CIA2c diff --git a/assets/icons/Interface/Back3_45x8.png b/assets/icons/Interface/Back3_45x8.png deleted file mode 100644 index 6cb945f6297dda14d46e8bb2e77496a572c600fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3630 zcmaJ@c{o&U8$Y%}*>{pL;#HP0mNJvEFQY8e*hVGB7z}1<3}z%Lk&-Q2)@&s;v?-D; zDP)Txq3k3ymXIuc0hkA(-S{=jv4ZvO$7Bzku6~pf=E+LYQbZWfN!;j2~8O>9z^f9>-b3E4Rr2bch!Q#hf zLs@fk>+jdAr?RIuIvAZo5h9{0Ns|SB01O>v0Pj;sutzqw)I@MfAcwg@!jr8HK0Gf0 zV4jOX52xKX+TgfH1~>sqkLEtnrjehKRVWn~0F(p_gGHxyaU`OFW=+RtaiCcmxbN`D ziyuef!2aus^wU5*I8Q|e`9<9z<-vHz> zjlr7S^<^9pu1~bBI3sF!fZ%vcqL8r%5RqZ3-Ufj5IRUf1V(nZY8?HR9?z-U;cS=+x zk^`u3O>Iv<+N~pAnfgXHWO06YRB^N+;DIStYQL~I-#9osJxfUpImKnuu^Rwn*Sel> z=^G_ZO^%ODdQSzgzP&8i@}DPz4HXxLwyLjkas$G4oveQMiHTk=N)yNZpgRAO6F|Q+ z;5)SDrZd2ocbxxC=vpj8f5UihZa%8nKz-`_Q=`{1Oy#FYii$Kyuw$g0UJ0(4Jm21_0HcrBB{Yk+ONk3jpSM=MUUB*}40|Yef0*yraQ(X_VDLRrh9b4YLprxX zx;mPV+oYLS466u_5%G-g19$LBWyEuXu}Ix(5k;Ws81ZA^m(A27&Jt6bq z*U`^GG~n!&cw9smRM7kC2kgnKmn=LkV`NpDBykfMJ?8{HoIK`Gu!LNFE|$f+k25wx zY?k-bMd5~k3yFzV7#ob)ZRJ^|a?vVY7rw}g(1yRj$+zvxl|x0>8tIF%p?+9K8^D}j9`M& zlPVI0678?8x4amy5Qo2v8MWan*oQ7k5j~=ZH=5wSc|+SYBk#_!_LAIpymw;YH;!8q z^MX+jhf5`5x_Y$TY2r?_=a%i}W|?-Tt3=cCNv}my;i=B4 z;i*%19GwE4jCvS%b~)a461scJ`RpxAr$?x<625nmcY8|Yot~8(EbBmrpc7D?$J`5R z-8V1%zzIBzj)Mix5aDqhLNDN4T5#xGz@d#TP`GwS}=Z9PvsT${BC=2@2uhMci+ zFblln&Gb(4&Y3*j&LQ~f!SFonL-vsl#SVdvGa6hPMj8)#{Yt${!%GkKhxXU@d(Khk zB;~e#zWjGPO8pKz1u5x2O1LdRKLozooUcHLDLY`J*)Na89*k{D>rV@7(mTV-B%HgskY1Vu-4`W|B`>C3eB^xc19O)2q;4HeY!yupPM2~Z7GEhQ-3V<>osj)n z)&2PGa$(l9tijR`#CIRt5HHW(UANWDl525$fnb?@n_tC@o^BhNPKiB?~rgf`PQ1vblVf&m|?4c zm`VkLOor6jS@Hb`wC`wsIacmKJ55_v?`4j=uKU!)FC>&)eR_Dn?+G>7hv^r94#3T3 zRYp|asXR}=^xa}1U6dxak8kescw=89;__G^=TtzZPWE`FV;T)tHZfT6RBCd%qcu0g zz*WyRF-$+|x@h`Cqlrk6DGDNOKT%Awl^0vQm=$GjQCkpKaO^=v(z0kwZNqTiGV1ZuEKJ38A?#n269`CenR>Ly%Jf2JpN8+$b0L{ zn$oTdl3_K8_feJiAN5tzT^EMIbr#Pgm0}&FLNuXn=qfKdVZQrpt8eSWv`y3~2JN)y z#~56UM@M$wylJL(!9_Q*2`P>1iRCgwYdAscUeap(p zQ5QS+p0H&GvA_S*k&4%IGsu~u84`2VweH@L8b6bsH_`I~rE&{#6LRjvBcEvbp?=Hp z?d9#S+qv|xdUGe*o-hX%^A8rTgyx)cYjxf%y<1WHL8ZU9?qkTACBaB}iNl{*DYMm& z)#xt%H5O-=N!?#Bd`yt_l&h+n4F6bZ6=MG~H{s09Gdou-STlY!a=_rdy1IvaMD)S9 zy+}A^A}fO2pEZ{m@|be`fMA2k#Ph#~ZiSs0Lw(Ngcro~*eJJBx#WL>f zn~e;Aw_|QKK4zV{MpCq|HWluxElZ!K65QsiDc``Wo&(AiM@tc92&-_)<_PlM3Gxhn zwRO(Lpp5p-cII#w>+QK8e)TbOQpDk z90dhA0oj^Gua4xA;)lgzov*{4e==qQ?@kSEj;DjqQLB75RN)VQg*SI5JTayKSRM zCsNpG05CA3Q?NLHJPqWD_aTrFVAiubFo@uV0K4efYS~iE@V*3OC>8GzYUhXx^~b@y zz($5313H{dK*ZCqAUZLC6bPpyz<=|?+48m;0tWqULi0y}{~Z*@76mdRQ}Lifnotd# zmX;Ri5KI&Ah1K@d@y6oRLE2i{PzaQLAJTvxf@|x*p)k-t7ntpk>g5ebn_K?lj=e*G zeQ7ib90Cam3DFGE(IiuSAW#?#2GP=nXlrY*Ei?idBpQ~kK?+p*#bAyP#8C+p8i7m# zZ8Ks$$w4#(m>ulDvmjD_(~<)JsT8|r5IU9ufof`PXZ6d_*7pCK5{bX918Hddf8zb0 z#DR_s3Lb*S2ahuwK8hCR%^QZVv>J!uD*ArxJqkUY1lc5%hP~aKfKsG5;gq zKUl9n#sahWBNoCA2D07T|LXNG6?=fT#oxor-uxbaJc&K?RQ7PQPTCi;Ka3qH8%K+u zKYy}Cr-rxT?B%evxrrma-_ygx!^?Ol-CP-<%pVta zcXsBP=Y8IfdG}DPx2wMP{#t?{>LcCZKKyRMcT>$B__;5wF5%bRX7>{|LDX$`HYFt1ku)>H-Rz?9dZaJbt6dq{@FQ-)YTxh+ZW}c zW(cPA?r{tDkN3uv@nJ<$srGGTTVBQl8R!5qpGh0GoDWh3UKx+wX_g`j5N9|@b+`t} zL^MW*kOfJR;b?{91kx`tyy*3cTq`MX0>^SbmKSJVlsT`=`^nNpVQZGE$$jCSC0qC_ zNTnRdlvy^H%P~0*gRCUWOOnKL0xJkKM$q<{;eb4C*iB`IFtim*Hys@rq{|3~P}T`j zSZUFPj9H;I>{6I;z}P%6S)Sosm&(9sbaiMZQ$gEK=P0&P(tAr{J3eMYwh!7UYbmgE z6dF!b*_f(S_L^BMUGP*@SeS+x470H?zGBKuAqUwhbQ?b^%N1`-Ib=cLAS;egx}2z3 znS~U1Mj)Ga>xPPQ_EuL>X#)%c2L>seHJ;`?G#`lbURempoYcZ|GRGC6QCtTqaKPHY zNY%BmwLqg$IbzrjFccUG2PrIy(REe!DMJCt2R*dvR|Hz|dNn%0a~#e2A>af>^zeRP zK^NX1MoQKVpS!1(Ty|517=tV#+PNr*cvOS#UlZZ9e(h5dKF3TrF+RvC_)p*yT}Vf<0) zcdBbj$Mm&c#E2PVf5s zEq3dI~(WbegwYst$NZyG}PgIe%nsW`Dgk*f6pFljN1d zFOI*ey*4rYLULiIVJgvf=bi&2-*$&PV6#o^pSBk=w%a diff --git a/assets/icons/iButton/DolphinExcited_64x63.png b/assets/icons/iButton/DolphinExcited_64x63.png deleted file mode 100644 index e695c85f08109523700392e311416ab7413a624e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2304 zcmbVO32+lt7~ZzhR?1;1Xu&Bg3(6szJ#uxIMre~_YnxIa*eVKac3+Z(WH;_^o20dZ z6b38E;0%H=a+IMS2w0V4T2!iKP)3wPF0YoB0jxL_R7OtqB@MA62;2>PM{0={1A;GS>u9!j-L%Q)Ct_8WWhRj?!r(}uwW%dJ8Ab@fk(`% zP{5RmA_reFn`Z=U+@ok#kc9+3pmAu>=ap18Xu%@9ENq8|2@H)uwAmIcJ8TdwwHKpS zSpld?N8&t5(x{oyQ6_`IM2c$7m%rDiaip2y%Xc(vjhiB0zzU;rwNaH)34i3E%}jAZaSZQZcOGBWtqik#9pC zoBoFZD6QQdvvEsbe7=|oRm&}hVMGPFC0cca{D7DYRJlyyL2fy8b67YUKWkM0r^$*# zmc2JNs`!S>C{5{TbYzJr335Ok)olT=aT>5-aMviDq~YzLX;yDwN#i3F$&zFj)GiC6 zD|8c7?;tZ-(!}ab|A4}j5jc&zBUs>Bm#p|W7_jK$+<@>)ZVZjd$XaEutU$xiJAL=@ ze5Ij#)_D)_GYf2TC{h{hY$VS_Bl;g1HpghjwL*)8|Ju zIob)Q&utl1uKd5nxVz-⁢8g{(TPqj~G#*c@HPK0j$|XBp4!G-MY4!xH$tNbKNQ- zR5LOJ%WJq9tLv~6`(*$Uq*h?vUtGK5Qv`|U^KID<&kIMMDtNyc?NwdaIR~`NTYkP* z@;A@rZ2fWTsMP!Sn8-`_%^RE0QdvzM>p7*oy?@;Hw!oR>rt>X3{$O*E!OvgoOpQkx zr<@JNHQ62}*F10_e&EqP#{*=&a{SWe4Zj`y<&91usbj$On$dNNbm+l@SO@J%$=S87 zVRxbXohAH2Y0JfijEw^a>~hR_uViVqws&u6?6T@4s%}w5#_^dSsk-^&Pdn$nR9m@e z#Ll#yUo3BA>w9lTMyAbm?7!b%mD}vCnZI{ZsB&Au&pj-MB%%| z`=$-OXF-y5F!!eyCwd1ZW&~46t@ThdpLO!Rt8wHtkhOGv;@RtcJN71shfVI?2Zmo5 za-nkmGVi#$VU>f7e0%z-tn2A{{r6pZVNOYDXJdSOy>EI(!;Jdn`wR5CW95=mb!G9G z^;OrH0^yOZ8?TK_eEh66`B>T12kKqwz|o{3Z~T#jh9MiP_FLZUc>iq+(mr%*V?#~9 z=JvRKnWeQ`d(GbwswU2%!cI9TI7`rEyLHX+SED}IlR)^Lz{Rcttho=+L-#Mk(Hgkf=N5t zHfDd^nzZ)xsVhr%_6u67kF9ye+xT<#ERvM_jjw1rvG9YenXlzuJ(q&7%3Z-!^eSyz z_=9WmzW~;NICua6 diff --git a/assets/icons/iButton/iButtonDolphinSuccess_109x60.png b/assets/icons/iButton/iButtonDolphinSuccess_109x60.png deleted file mode 100644 index f234aba9043fc3ab21e63d5ae71fc1634ee50dd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2178 zcmbVOX;2eq7!H+M#M>TXCqvhjAEHh600LP_6%vk!5hPHt$JAwaLv~2AOLidv6j1SM z1r?Dxh%FS0bQHYt>Ub11^(tNrT57A{pcVnef+w|KIMPnX*49im`yJ2wy!Sg<88bU# zr03h75{YD_Hc}G{-e&Lwd%OaETO!N#;5CeooF_;m!(VqkZj#~>FNx&Ux7fG@F+n$r zpg6Ofq`5>!Za4D)4TPuLd6G(IL@1F-Vl67T^Ux6(VrdmTH$aE#cr{~WBQvawK4W$q zm61#-X?Xg3P_Uf<1ZGAgA-maR5eT~q?&BrE+Ift?P#;80R>2`og;0Vn22yiY22#jT z8HJ)a6sVMA3V(kE`Ywc{IEtVF2!_ir1%dh#SRgd;fq^zFZ6IPb;RCwBP6Zo9ktYzu zX0yp{esa#5gkVag5vkAUVEdv6Kh7=hU46=sFP#k>YaGXFDguh}ch7u^+57mJ> zph=PZZ(xpQ4e2k0(iCCftY#8Ki8Yf+48mKI`n2hEgw`U6q=jO%8Wjvom9s2OD5$_7 z45e`yi75kQ3P1lqSrAGYWC0kiV3a7T3?Tch1@an>N_A#%P@evu^3hfnbP#EJc^+4W z2sLNotU8VcQTBtOKhL#lb&QoWuqJRJ#72Zd+Ay^OQz{fPT#h*ly|3ViNLBz@O&@3- zJ(Du9GOJ53+oO6 z#v{&#?e_!uyNi8ye`)SPIG7mDrE64yjceF37BJzgV3cwAh~W~+ka1c~NSwW-z4&TI zE#$E*HM;Xz(e5F0+h(|p9btGf>amG^n)z9d&!dCYbXkdC=IVw7x9$&2pWM@W>8>s) z+LfrSx_x^^@aS7o$F8i}71b_B5U`G|p&!oY;B!Kuz00pOopP zUinW(-iRrD!*M9>%F4;hrJbqqjYKYq zN1UvvesFDRLtE?Qrp4*A>h(T(e(rUbE|*ueYlgnIc~xWMhuxn#I)+=8RB5CvWnz*G zn4ccA4eiEl>syN+l|PE@g^FztXHE&+BU?t4)p=8em7XWJ9ge;}Wy{_8+&O8x0~KFS z4a{vdX6`?s$r+hO?iHXh^(mWmwZ1!Qrq0to90p(W%sM^fU8${LXF9dWFsZQVLSc7{ zzDYgpX2;B??1&{@Ju9vT*PQCD-@k53@M7;AetFj-uT1v|(&Ug;&t~ep0WgOKN zAz91k?~?{M$6*^5=Cy;(sEaM}TlUMU3X*5l~Aqu$#~`BuGhkcd3$QpeCp@ebBU;+33uKf9lqk;kI%kRrPZJRZg*#; zadY+$shLI8){aj-_9g6Fa9~_-ulG`)vhuC-qT`K4N4lla{wL?{lYi2()m-{8#E|S> zz*dbnHpIT8dcS4F$J>xsz8+f>eJ1qhnTa9#9l3!`b;)birXJbUUbTMxn;Z4jyUt`j z#ZUPjnHc@ih=}aCZx7!S?@IF@c<=n;=p4IyQPkQCn?(7l9%JSD@ey}R3$n5f75o;K zv*Bq_+Qf=qwtv2DbXl9Dc3sxfa))yJtj3;4?zb-9HW5wt^Mi)e`2Ak!(Q+|F^lY0J nlt1Pie{4%#{jRRld57H^dV=R~_ifK|{!7q?&DK Date: Thu, 19 May 2022 22:53:33 +0300 Subject: [PATCH 06/28] Drop libs rtc (#1248) --- applications/dolphin/helpers/dolphin_state.c | 13 +----- firmware/targets/f7/furi_hal/furi_hal_rtc.c | 45 +++++++++++++++++++ .../targets/furi_hal_include/furi_hal_rtc.h | 2 + 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/applications/dolphin/helpers/dolphin_state.c b/applications/dolphin/helpers/dolphin_state.c index 86287a2a..8a569392 100644 --- a/applications/dolphin/helpers/dolphin_state.c +++ b/applications/dolphin/helpers/dolphin_state.c @@ -72,19 +72,8 @@ bool dolphin_state_load(DolphinState* dolphin_state) { uint64_t dolphin_state_timestamp() { FuriHalRtcDateTime datetime; - struct tm current; - furi_hal_rtc_get_datetime(&datetime); - - current.tm_year = datetime.year - 1900; - current.tm_mday = datetime.day; - current.tm_mon = datetime.month - 1; - - current.tm_hour = datetime.hour; - current.tm_min = datetime.minute; - current.tm_sec = datetime.second; - - return mktime(¤t); + return furi_hal_rtc_datetime_to_timestamp(&datetime); } bool dolphin_state_is_levelup(uint32_t icounter) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index b580a04b..df410a9f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -33,6 +33,20 @@ typedef struct { _Static_assert(sizeof(DeveloperReg) == 4, "DeveloperReg size mismatch"); +#define FURI_HAL_RTC_SECONDS_PER_MINUTE 60 +#define FURI_HAL_RTC_SECONDS_PER_HOUR (FURI_HAL_RTC_SECONDS_PER_MINUTE * 60) +#define FURI_HAL_RTC_SECONDS_PER_DAY (FURI_HAL_RTC_SECONDS_PER_HOUR * 24) +#define FURI_HAL_RTC_MONTHS_COUNT 12 +#define FURI_HAL_RTC_EPOCH_START_YEAR 1970 +#define FURI_HAL_RTC_IS_LEAP_YEAR(year) \ + ((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0)) + +static const uint8_t furi_hal_rtc_days_per_month[][FURI_HAL_RTC_MONTHS_COUNT] = { + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; + +static const uint16_t furi_hal_rtc_days_per_year[] = {365, 366}; + void furi_hal_rtc_init_early() { // LSE and RTC LL_PWR_EnableBkUpAccess(); @@ -259,3 +273,34 @@ void furi_hal_rtc_set_pin_fails(uint32_t value) { uint32_t furi_hal_rtc_get_pin_fails() { return furi_hal_rtc_get_register(FuriHalRtcRegisterPinFails); } + +uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime) { + uint32_t timestamp = 0; + uint8_t years = 0; + uint8_t leap_years = 0; + + for(uint16_t y = FURI_HAL_RTC_EPOCH_START_YEAR; y < datetime->year; y++) { + if(FURI_HAL_RTC_IS_LEAP_YEAR(y)) { + leap_years++; + } else { + years++; + } + } + + timestamp += + ((years * furi_hal_rtc_days_per_year[0]) + (leap_years * furi_hal_rtc_days_per_year[1])) * + FURI_HAL_RTC_SECONDS_PER_DAY; + + uint8_t year_index = (FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year)) ? 1 : 0; + + for(uint8_t m = 0; m < (datetime->month - 1); m++) { + timestamp += furi_hal_rtc_days_per_month[year_index][m] * FURI_HAL_RTC_SECONDS_PER_DAY; + } + + timestamp += (datetime->day - 1) * FURI_HAL_RTC_SECONDS_PER_DAY; + timestamp += datetime->hour * FURI_HAL_RTC_SECONDS_PER_HOUR; + timestamp += datetime->minute * FURI_HAL_RTC_SECONDS_PER_MINUTE; + timestamp += datetime->second; + + return timestamp; +} diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index 21c69b24..bdae3b93 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rtc.h +++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h @@ -93,6 +93,8 @@ void furi_hal_rtc_set_pin_fails(uint32_t value); uint32_t furi_hal_rtc_get_pin_fails(); +uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime); + #ifdef __cplusplus } #endif From 8d737133fe0438395c4f3bee94c5363f71d3cdd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 20 May 2022 21:40:49 +0300 Subject: [PATCH 07/28] FL-2534: change button text in format dialog and fix incorrect dialog_ex behavior when text is not set (#1253) --- applications/gui/modules/dialog_ex.c | 4 ++-- .../scenes/storage_settings_scene_format_confirm.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/gui/modules/dialog_ex.c b/applications/gui/modules/dialog_ex.c index c01fb4c5..dee2a097 100755 --- a/applications/gui/modules/dialog_ex.c +++ b/applications/gui/modules/dialog_ex.c @@ -45,8 +45,8 @@ static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { } // Draw header + canvas_set_font(canvas, FontPrimary); if(model->header.text != NULL) { - canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned( canvas, model->header.x, @@ -57,8 +57,8 @@ static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { } // Draw text + canvas_set_font(canvas, FontSecondary); if(model->text.text != NULL) { - canvas_set_font(canvas, FontSecondary); elements_multiline_text_aligned( canvas, model->text.x, diff --git a/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c b/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c index db040d6e..4a83bd51 100644 --- a/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c +++ b/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c @@ -11,7 +11,7 @@ void storage_settings_scene_format_confirm_on_enter(void* context) { StorageSettings* app = context; FS_Error sd_status = storage_sd_status(app->fs_api); DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_left_button_text(dialog_ex, "Back"); + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); if(sd_status == FSE_NOT_READY) { dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); From 8a81b79e009a53d09f2d48243d58f6e133aed298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 20 May 2022 23:03:09 +0300 Subject: [PATCH 08/28] Infrared: fix RAW parsing in cli. Remove dead sources. (#1255) * Infrared: fix RAW parsing in cli. Remove dead sources. --- applications/infrared/cli/infrared_cli.cpp | 5 +- lib/app-scened-template/file_worker.c | 443 -------------------- lib/app-scened-template/file_worker.h | 249 ----------- lib/app-scened-template/file_worker_cpp.cpp | 94 ----- lib/app-scened-template/file_worker_cpp.h | 190 --------- lib/app-template/app_template.cpp | 102 ----- lib/app-template/app_template.h | 113 ----- 7 files changed, 2 insertions(+), 1194 deletions(-) delete mode 100644 lib/app-scened-template/file_worker.c delete mode 100644 lib/app-scened-template/file_worker.h delete mode 100644 lib/app-scened-template/file_worker_cpp.cpp delete mode 100644 lib/app-scened-template/file_worker_cpp.h delete mode 100644 lib/app-template/app_template.cpp delete mode 100644 lib/app-template/app_template.h diff --git a/applications/infrared/cli/infrared_cli.cpp b/applications/infrared/cli/infrared_cli.cpp index b60e4599..79ab987d 100644 --- a/applications/infrared/cli/infrared_cli.cpp +++ b/applications/infrared/cli/infrared_cli.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -86,7 +85,7 @@ static void infrared_cli_print_usage(void) { } printf("\r\n"); printf("\tRaw format:\r\n"); - printf("\tir_tx RAW F: DC: ...\r\n"); + printf("\tir tx RAW F: DC: ...\r\n"); printf( "\tFrequency (%d - %d), Duty cycle (0 - 100), max 512 samples\r\n", INFRARED_MIN_FREQUENCY, @@ -178,7 +177,7 @@ static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) { break; } if(string_get_cstr(args)[size] == ' ') { - string_right(args, size); + string_right(args, size + 1); break; } } diff --git a/lib/app-scened-template/file_worker.c b/lib/app-scened-template/file_worker.c deleted file mode 100644 index 3a742ff9..00000000 --- a/lib/app-scened-template/file_worker.c +++ /dev/null @@ -1,443 +0,0 @@ -#include "file_worker.h" -#include -#include -#include -#include - -struct FileWorker { - Storage* api; - bool silent; - File* file; -}; - -bool file_worker_check_common_errors(FileWorker* file_worker); -void file_worker_show_error_internal(FileWorker* file_worker, const char* error_text); -bool file_worker_read_internal(FileWorker* file_worker, void* buffer, uint16_t bytes_to_read); -bool file_worker_write_internal( - FileWorker* file_worker, - const void* buffer, - uint16_t bytes_to_write); -bool file_worker_tell_internal(FileWorker* file_worker, uint64_t* position); -bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool from_start); - -FileWorker* file_worker_alloc(bool _silent) { - FileWorker* file_worker = malloc(sizeof(FileWorker)); - file_worker->silent = _silent; - file_worker->api = furi_record_open("storage"); - file_worker->file = storage_file_alloc(file_worker->api); - - return file_worker; -} - -void file_worker_free(FileWorker* file_worker) { - storage_file_free(file_worker->file); - furi_record_close("storage"); - free(file_worker); -} - -bool file_worker_open( - FileWorker* file_worker, - const char* filename, - FS_AccessMode access_mode, - FS_OpenMode open_mode) { - bool result = storage_file_open(file_worker->file, filename, access_mode, open_mode); - - if(!result) { - file_worker_show_error_internal(file_worker, "Cannot open\nfile"); - return false; - } - - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_close(FileWorker* file_worker) { - if(storage_file_is_open(file_worker->file)) { - storage_file_close(file_worker->file); - } - - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_mkdir(FileWorker* file_worker, const char* dirname) { - FS_Error fs_result = storage_common_mkdir(file_worker->api, dirname); - - if(fs_result != FSE_OK && fs_result != FSE_EXIST) { - file_worker_show_error_internal(file_worker, "Cannot create\nfolder"); - return false; - }; - - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_remove(FileWorker* file_worker, const char* filename) { - FS_Error fs_result = storage_common_remove(file_worker->api, filename); - if(fs_result != FSE_OK && fs_result != FSE_NOT_EXIST) { - file_worker_show_error_internal(file_worker, "Cannot remove\nold file"); - return false; - }; - - return file_worker_check_common_errors(file_worker); -} - -void file_worker_get_next_filename( - FileWorker* file_worker, - const char* dirname, - const char* filename, - const char* fileextension, - string_t nextfilename) { - string_t temp_str; - string_init(temp_str); - uint16_t num = 0; - - string_printf(temp_str, "%s/%s%s", dirname, filename, fileextension); - - while(storage_common_stat(file_worker->api, string_get_cstr(temp_str), NULL) == FSE_OK) { - num++; - string_printf(temp_str, "%s/%s%d%s", dirname, filename, num, fileextension); - } - - if(num) { - string_printf(nextfilename, "%s%d", filename, num); - } else { - string_printf(nextfilename, "%s", filename); - } - - string_clear(temp_str); -} - -bool file_worker_read(FileWorker* file_worker, void* buffer, uint16_t bytes_to_read) { - if(!file_worker_read_internal(file_worker, buffer, bytes_to_read)) { - return false; - } - - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_read_until(FileWorker* file_worker, string_t str_result, char separator) { - string_reset(str_result); - const uint8_t buffer_size = 32; - uint8_t buffer[buffer_size]; - - do { - uint16_t read_count = storage_file_read(file_worker->file, buffer, buffer_size); - if(storage_file_get_error(file_worker->file) != FSE_OK) { - file_worker_show_error_internal(file_worker, "Cannot read\nfile"); - return false; - } - - bool result = false; - for(uint16_t i = 0; i < read_count; i++) { - if(buffer[i] == separator) { - uint64_t position; - if(!file_worker_tell_internal(file_worker, &position)) { - return false; - } - - position = position - read_count + i + 1; - - if(!file_worker_seek_internal(file_worker, position, true)) { - return false; - } - - result = true; - break; - } else { - string_push_back(str_result, buffer[i]); - } - } - - if(result || read_count == 0) { - break; - } - } while(true); - - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_read_hex(FileWorker* file_worker, uint8_t* buffer, uint16_t bytes_to_read) { - uint8_t hi_nibble_value, low_nibble_value; - uint8_t text[2]; - - for(uint8_t i = 0; i < bytes_to_read; i++) { - if(i != 0) { - // space - if(!file_worker_read_internal(file_worker, text, 1)) { - return false; - } - } - - // actual data - if(!file_worker_read_internal(file_worker, text, 2)) { - return false; - } - - // convert hex value to byte - if(hex_char_to_hex_nibble(text[0], &hi_nibble_value) && - hex_char_to_hex_nibble(text[1], &low_nibble_value)) { - buffer[i] = (hi_nibble_value << 4) | low_nibble_value; - } else { - file_worker_show_error_internal(file_worker, "Cannot parse\nfile"); - return false; - } - } - - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_tell(FileWorker* file_worker, uint64_t* position) { - if(!file_worker_tell_internal(file_worker, position)) { - return false; - } - - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_seek(FileWorker* file_worker, uint64_t position, bool from_start) { - if(!file_worker_seek_internal(file_worker, position, from_start)) { - return false; - } - - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_write(FileWorker* file_worker, const void* buffer, uint16_t bytes_to_write) { - if(!file_worker_write_internal(file_worker, buffer, bytes_to_write)) { - return false; - } - - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_write_hex(FileWorker* file_worker, const uint8_t* buffer, uint16_t bytes_to_write) { - const uint8_t byte_text_size = 3; - char byte_text[byte_text_size]; - - for(uint8_t i = 0; i < bytes_to_write; i++) { - sniprintf(byte_text, byte_text_size, "%02X", buffer[i]); - - if(i != 0) { - // space - const char* space = " "; - if(!file_worker_write_internal(file_worker, space, 1)) { - return false; - } - } - - if(!file_worker_write_internal(file_worker, byte_text, 2)) { - return false; - } - } - - return file_worker_check_common_errors(file_worker); -} - -void file_worker_show_error(FileWorker* file_worker, const char* error_text) { - UNUSED(file_worker); - DialogsApp* dialogs = furi_record_open("dialogs"); - - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_text(message, error_text, 88, 32, AlignCenter, AlignCenter); - dialog_message_set_icon(message, &I_SDQuestion_35x43, 5, 6); - dialog_message_set_buttons(message, "Back", NULL, NULL); - dialog_message_show(dialogs, message); - dialog_message_free(message); - - furi_record_close("dialogs"); -} - -bool file_worker_file_select( - FileWorker* file_worker, - const char* path, - const char* extension, - char* result, - uint8_t result_size, - const char* selected_filename) { - UNUSED(file_worker); - DialogsApp* dialogs = furi_record_open("dialogs"); - bool ret = - dialog_file_select_show(dialogs, path, extension, result, result_size, selected_filename); - furi_record_close("dialogs"); - return ret; -} - -bool file_worker_check_common_errors(FileWorker* file_worker) { - UNUSED(file_worker); - //TODO remove - /* TODO: [FL-1431] Add return value to file_parser.get_sd_api().check_error() and replace get_fs_info(). */ - return true; -} - -void file_worker_show_error_internal(FileWorker* file_worker, const char* error_text) { - if(!file_worker->silent) { - file_worker_show_error(file_worker, error_text); - } -} - -bool file_worker_read_internal(FileWorker* file_worker, void* buffer, uint16_t bytes_to_read) { - uint16_t read_count = storage_file_read(file_worker->file, buffer, bytes_to_read); - - if(storage_file_get_error(file_worker->file) != FSE_OK || read_count != bytes_to_read) { - file_worker_show_error_internal(file_worker, "Cannot read\nfile"); - return false; - } - - return true; -} - -bool file_worker_write_internal( - FileWorker* file_worker, - const void* buffer, - uint16_t bytes_to_write) { - uint16_t write_count = storage_file_write(file_worker->file, buffer, bytes_to_write); - - if(storage_file_get_error(file_worker->file) != FSE_OK || write_count != bytes_to_write) { - file_worker_show_error_internal(file_worker, "Cannot write\nto file"); - return false; - } - - return true; -} - -bool file_worker_tell_internal(FileWorker* file_worker, uint64_t* position) { - *position = storage_file_tell(file_worker->file); - - if(storage_file_get_error(file_worker->file) != FSE_OK) { - file_worker_show_error_internal(file_worker, "Cannot tell\nfile offset"); - return false; - } - - return true; -} - -bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool from_start) { - storage_file_seek(file_worker->file, position, from_start); - if(storage_file_get_error(file_worker->file) != FSE_OK) { - file_worker_show_error_internal(file_worker, "Cannot seek\nfile"); - return false; - } - - return true; -} - -bool file_worker_read_until_buffered( - FileWorker* file_worker, - string_t str_result, - char* file_buf, - size_t* file_buf_cnt, - size_t file_buf_size, - char separator) { - furi_assert(string_capacity(str_result) > 0); - - // fs_api->file.read now supports up to 512 bytes reading at a time - furi_assert(file_buf_size <= 512); - - string_reset(str_result); - size_t newline_index = 0; - bool found_eol = false; - bool max_length_exceeded = false; - size_t max_length = string_capacity(str_result) - 1; - - while(1) { - if(*file_buf_cnt > 0) { - size_t end_index = 0; - char* endline_ptr = (char*)memchr(file_buf, separator, *file_buf_cnt); - newline_index = endline_ptr - file_buf; - - if(endline_ptr == 0) { - end_index = *file_buf_cnt; - } else if(newline_index < *file_buf_cnt) { - end_index = newline_index + 1; - found_eol = true; - } else { - furi_assert(0); - } - - if(max_length && (string_size(str_result) + end_index > max_length)) - max_length_exceeded = true; - - if(!max_length_exceeded) { - for(size_t i = 0; i < end_index; ++i) { - string_push_back(str_result, file_buf[i]); - } - } - - memmove(file_buf, &file_buf[end_index], *file_buf_cnt - end_index); - *file_buf_cnt = *file_buf_cnt - end_index; - if(found_eol) break; - } - - *file_buf_cnt += storage_file_read( - file_worker->file, &file_buf[*file_buf_cnt], file_buf_size - *file_buf_cnt); - if(storage_file_get_error(file_worker->file) != FSE_OK) { - file_worker_show_error_internal(file_worker, "Cannot read\nfile"); - string_reset(str_result); - *file_buf_cnt = 0; - break; - } - if(*file_buf_cnt == 0) { - break; // end of reading - } - } - - if(max_length_exceeded) string_reset(str_result); - - return string_size(str_result) || *file_buf_cnt; -} - -bool file_worker_get_value_from_key( - FileWorker* file_worker, - string_t key, - char delimiter, - string_t value) { - bool found = false; - string_t next_line; - string_t next_key; - string_init(next_line); - string_init(next_key); - size_t delim_pos = 0; - - while(file_worker_read_until(file_worker, next_line, '\n')) { - delim_pos = string_search_char(next_line, delimiter); - if(delim_pos == STRING_FAILURE) { - break; - } - string_set_n(next_key, next_line, 0, delim_pos); - if(string_equal_p(next_key, key)) { - string_right(next_line, delim_pos); - string_strim(next_line); - string_set(value, next_line); - found = true; - break; - } - } - - string_clear(next_line); - string_clear(next_key); - return found; -} - -bool file_worker_rename(FileWorker* file_worker, const char* old_path, const char* new_path) { - FS_Error fs_result = storage_common_rename(file_worker->api, old_path, new_path); - - if(fs_result != FSE_OK && fs_result != FSE_EXIST) { - file_worker_show_error_internal(file_worker, "Cannot rename\n file/directory"); - return false; - } - - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_check_errors(FileWorker* file_worker) { - return file_worker_check_common_errors(file_worker); -} - -bool file_worker_is_file_exist(FileWorker* file_worker, const char* filename, bool* exist) { - File* file = storage_file_alloc(file_worker->api); - - *exist = storage_file_open(file, filename, FSAM_READ, FSOM_OPEN_EXISTING); - storage_file_close(file); - storage_file_free(file); - - bool result = file_worker_check_common_errors(file_worker); - return result; -} diff --git a/lib/app-scened-template/file_worker.h b/lib/app-scened-template/file_worker.h deleted file mode 100644 index 1b5f3407..00000000 --- a/lib/app-scened-template/file_worker.h +++ /dev/null @@ -1,249 +0,0 @@ -#pragma once -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief File operations helper class. - * Automatically opens API records, shows error message on error. - */ -typedef struct FileWorker FileWorker; - -/** - * @brief Allocate FileWorker - * - * @param silent do not show errors except from file_worker_show_error fn - * @return FileWorker* - */ -FileWorker* file_worker_alloc(bool silent); - -/** - * @brief free FileWorker - * - * @param file_worker - */ -void file_worker_free(FileWorker* file_worker); - -/** - * @brief Open file - * - * @param file_worker FileWorker instance - * @param filename - * @param access_mode - * @param open_mode - * @return true on success - */ -bool file_worker_open( - FileWorker* file_worker, - const char* filename, - FS_AccessMode access_mode, - FS_OpenMode open_mode); - -/** - * @brief Close file - * - * @param file_worker FileWorker instance - * @return true on success - */ -bool file_worker_close(FileWorker* file_worker); - -/** - * @brief Creates a directory. Doesn't show error if directory exist. - * - * @param file_worker FileWorker instance - * @param dirname - * @return true on success - */ -bool file_worker_mkdir(FileWorker* file_worker, const char* dirname); - -/** - * @brief Removes the file. Doesn't show error if file doesn't exist. - * - * @param file_worker FileWorker instance - * @param filename - * @return true on success - */ -bool file_worker_remove(FileWorker* file_worker, const char* filename); - -/** - * @brief Get next free filename. - * - * @param file_worker FileWorker instance - * @param dirname - * @param filename - * @param fileextension - * @param nextfilename return name - */ -void file_worker_get_next_filename( - FileWorker* file_worker, - const char* dirname, - const char* filename, - const char* fileextension, - string_t nextfilename); - -/** - * @brief Reads data from a file. - * - * @param file_worker FileWorker instance - * @param buffer - * @param bytes_to_read - * @return true on success - */ -bool file_worker_read(FileWorker* file_worker, void* buffer, uint16_t bytes_to_read); - -/** - * @brief Reads data from a file until separator or EOF is found. - * Moves seek pointer to the next symbol after the separator. The separator is not included in the result. - * - * @param file_worker FileWorker instance - * @param result - * @param separator - * @return true on success - */ -bool file_worker_read_until(FileWorker* file_worker, string_t result, char separator); - -/** - * @brief Reads data in hexadecimal space-delimited format. For example "AF FF" in a file - [175, 255] in a read buffer. - * - * @param file_worker FileWorker instance - * @param buffer - * @param bytes_to_read - * @return true on success - */ -bool file_worker_read_hex(FileWorker* file_worker, uint8_t* buffer, uint16_t bytes_to_read); - -/** - * @brief Read seek pointer value - * - * @param file_worker FileWorker instance - * @param position - * @return true on success - */ -bool file_worker_tell(FileWorker* file_worker, uint64_t* position); - -/** - * @brief Set seek pointer value - * - * @param file_worker FileWorker instance - * @param position - * @param from_start - * @return true on success - */ -bool file_worker_seek(FileWorker* file_worker, uint64_t position, bool from_start); - -/** - * @brief Write data to file. - * - * @param file_worker FileWorker instance - * @param buffer - * @param bytes_to_write - * @return true on success - */ -bool file_worker_write(FileWorker* file_worker, const void* buffer, uint16_t bytes_to_write); - -/** - * @brief Write data to file in hexadecimal space-delimited format. For example [175, 255] in a write buffer - "AF FF" in a file. - * - * @param file_worker FileWorker instance - * @param buffer - * @param bytes_to_write - * @return true on success - */ -bool file_worker_write_hex(FileWorker* file_worker, const uint8_t* buffer, uint16_t bytes_to_write); - -/** - * @brief Show system file error message - * - * @param file_worker FileWorker instance - * @param error_text - */ -void file_worker_show_error(FileWorker* file_worker, const char* error_text); - -/** - * @brief Show system file select widget - * - * @param file_worker FileWorker instance - * @param path path to directory - * @param extension file extension to be offered for selection - * @param selected_filename buffer where the selected filename will be saved - * @param selected_filename_size and the size of this buffer - * @param preselected_filename filename to be preselected - * @return bool whether a file was selected - */ -bool file_worker_file_select( - FileWorker* file_worker, - const char* path, - const char* extension, - char* selected_filename, - uint8_t selected_filename_size, - const char* preselected_filename); - -/** - * @brief Reads data from a file until separator or EOF is found. - * The separator is included in the result. - * - * @param file_worker FileWorker instance - * @param str_result - * @param file_buf - * @param file_buf_cnt - * @param max_length - * @param separator - * @return true on success - */ -bool file_worker_read_until_buffered( - FileWorker* file_worker, - string_t str_result, - char* file_buf, - size_t* file_buf_cnt, - size_t max_length, - char separator); - -/** - * @brief Gets value from key - * - * @param file_worker FileWorker instance - * @param key key - * @param delimeter key-value delimeter - * @param value value for given key - * @return true on success - */ -bool file_worker_get_value_from_key( - FileWorker* file_worker, - string_t key, - char delimiter, - string_t value); - -/** - * @brief Check whether file exist or not - * - * @param file_worker FileWorker instance - * @param filename - * @param exist - flag to show file exist - * @return true on success - */ -bool file_worker_is_file_exist(FileWorker* file_worker, const char* filename, bool* exist); - -/** - * @brief Rename file or directory - * - * @param file_worker FileWorker instance - * @param old_filename - * @param new_filename - * @return true on success - */ -bool file_worker_rename(FileWorker* file_worker, const char* old_path, const char* new_path); - -/** - * @brief Check errors - * - * @param file_worker FileWorker instance - * @return true on success - */ -bool file_worker_check_errors(FileWorker* file_worker); - -#ifdef __cplusplus -} -#endif diff --git a/lib/app-scened-template/file_worker_cpp.cpp b/lib/app-scened-template/file_worker_cpp.cpp deleted file mode 100644 index 59a08a01..00000000 --- a/lib/app-scened-template/file_worker_cpp.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "file_worker_cpp.h" -#include - -FileWorkerCpp::FileWorkerCpp(bool _silent) { - file_worker = file_worker_alloc(_silent); -} - -FileWorkerCpp::~FileWorkerCpp() { - file_worker_free(file_worker); -} - -bool FileWorkerCpp::open(const char* filename, FS_AccessMode access_mode, FS_OpenMode open_mode) { - return file_worker_open(file_worker, filename, access_mode, open_mode); -} - -bool FileWorkerCpp::close() { - return file_worker_close(file_worker); -} - -bool FileWorkerCpp::mkdir(const char* dirname) { - return file_worker_mkdir(file_worker, dirname); -} - -bool FileWorkerCpp::remove(const char* filename) { - return file_worker_remove(file_worker, filename); -} - -bool FileWorkerCpp::read(void* buffer, uint16_t bytes_to_read) { - return file_worker_read(file_worker, buffer, bytes_to_read); -} - -bool FileWorkerCpp::read_until(string_t str_result, char separator) { - return file_worker_read_until(file_worker, str_result, separator); -} - -bool FileWorkerCpp::read_hex(uint8_t* buffer, uint16_t bytes_to_read) { - return file_worker_read_hex(file_worker, buffer, bytes_to_read); -} - -bool FileWorkerCpp::tell(uint64_t* position) { - return file_worker_tell(file_worker, position); -} - -bool FileWorkerCpp::seek(uint64_t position, bool from_start) { - return file_worker_seek(file_worker, position, from_start); -} - -bool FileWorkerCpp::write(const void* buffer, uint16_t bytes_to_write) { - return file_worker_write(file_worker, buffer, bytes_to_write); -} - -bool FileWorkerCpp::write_hex(const uint8_t* buffer, uint16_t bytes_to_write) { - return file_worker_write_hex(file_worker, buffer, bytes_to_write); -} - -void FileWorkerCpp::show_error(const char* error_text) { - file_worker_show_error(file_worker, error_text); -} - -bool FileWorkerCpp::file_select( - const char* path, - const char* extension, - char* result, - uint8_t result_size, - const char* selected_filename) { - return file_worker_file_select( - file_worker, path, extension, result, result_size, selected_filename); -} - -bool FileWorkerCpp::read_until_buffered( - string_t str_result, - char* file_buf, - size_t* file_buf_cnt, - size_t max_length, - char separator) { - return file_worker_read_until_buffered( - file_worker, str_result, file_buf, file_buf_cnt, max_length, separator); -} - -bool FileWorkerCpp::get_value_from_key(string_t key, char delimiter, string_t value) { - return file_worker_get_value_from_key(file_worker, key, delimiter, value); -} - -bool FileWorkerCpp::is_file_exist(const char* filename, bool* exist) { - return file_worker_is_file_exist(file_worker, filename, exist); -} - -bool FileWorkerCpp::rename(const char* old_path, const char* new_path) { - return file_worker_rename(file_worker, old_path, new_path); -} - -bool FileWorkerCpp::check_errors() { - return file_worker_check_errors(file_worker); -} diff --git a/lib/app-scened-template/file_worker_cpp.h b/lib/app-scened-template/file_worker_cpp.h deleted file mode 100644 index eba622af..00000000 --- a/lib/app-scened-template/file_worker_cpp.h +++ /dev/null @@ -1,190 +0,0 @@ -#pragma once -#include "file_worker.h" - -/** - * @brief File operations helper class. - * Automatically opens API records, shows error message on error. - */ -class FileWorkerCpp { -public: - FileWorkerCpp(bool silent = false); - ~FileWorkerCpp(); - - /** - * @brief Open file - * - * @param filename - * @param access_mode - * @param open_mode - * @return true on success - */ - bool open(const char* filename, FS_AccessMode access_mode, FS_OpenMode open_mode); - - /** - * @brief Close file - * - * @return true on success - */ - bool close(); - - /** - * @brief Creates a directory. Doesn't show error if directory exist. - * - * @param dirname - * @return true on success - */ - bool mkdir(const char* dirname); - - /** - * @brief Removes the file. Doesn't show error if file doesn't exist. - * - * @param filename - * @return true on success - */ - bool remove(const char* filename); - - /** - * @brief Reads data from a file. - * - * @param buffer - * @param bytes_to_read - * @return true on success - */ - bool read(void* buffer, uint16_t bytes_to_read = 1); - - /** - * @brief Reads data from a file until separator or EOF is found. - * Moves seek pointer to the next symbol after the separator. The separator is not included in the result. - * - * @param result - * @param separator - * @return true on success - */ - bool read_until(string_t result, char separator = '\n'); - - /** - * @brief Reads data in hexadecimal space-delimited format. For example "AF FF" in a file - [175, 255] in a read buffer. - * - * @param buffer - * @param bytes_to_read - * @return true on success - */ - bool read_hex(uint8_t* buffer, uint16_t bytes_to_read = 1); - - /** - * @brief Read seek pointer value - * - * @param position - * @return true on success - */ - bool tell(uint64_t* position); - - /** - * @brief Set seek pointer value - * - * @param position - * @param from_start - * @return true on success - */ - bool seek(uint64_t position, bool from_start); - - /** - * @brief Write data to file. - * - * @param buffer - * @param bytes_to_write - * @return true on success - */ - bool write(const void* buffer, uint16_t bytes_to_write = 1); - - /** - * @brief Write data to file in hexadecimal space-delimited format. For example [175, 255] in a write buffer - "AF FF" in a file. - * - * @param buffer - * @param bytes_to_write - * @return true on success - */ - bool write_hex(const uint8_t* buffer, uint16_t bytes_to_write = 1); - - /** - * @brief Show system file error message - * - * @param error_text - */ - void show_error(const char* error_text); - - /** - * @brief Show system file select widget - * - * @param path - * @param extension - * @param result - * @param result_size - * @param selected_filename - * @return true if file was selected - */ - bool file_select( - const char* path, - const char* extension, - char* result, - uint8_t result_size, - const char* selected_filename); - - /** - * @brief Reads data from a file until separator or EOF is found. - * Moves seek pointer to the next symbol after the separator. The separator is included in the result. - * - * @param result - * @param file_buf - * @param file_buf_cnt - * @param max_length - * @param separator - * @return true on success - */ - bool read_until_buffered( - string_t str_result, - char* file_buf, - size_t* file_buf_cnt, - size_t max_length, - char separator = '\n'); - - /** - * @brief Gets value from key - * - * @param file_worker FileWorker instance - * @param key key - * @param delimeter key-value delimeter - * @param value value for given key - * @return true on success - */ - bool get_value_from_key(string_t key, char delimiter, string_t value); - - /** - * @brief Check whether file exist or not - * - * @param file_worker FileWorker instance - * @param filename - * @param exist - flag to show file exist - * @return true on success - */ - bool is_file_exist(const char* filename, bool* exist); - - /** - * @brief Rename file or directory - * - * @param old_filename - * @param new_filename - * @return true on success - */ - bool rename(const char* old_path, const char* new_path); - - /** - * @brief Check errors - * - * @return true if no errors - */ - bool check_errors(); - -private: - FileWorker* file_worker; -}; diff --git a/lib/app-template/app_template.cpp b/lib/app-template/app_template.cpp deleted file mode 100644 index ba73945c..00000000 --- a/lib/app-template/app_template.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "app_template.h" - -/* -To use this example you need to rename -AppExampleState - class to hold app state -AppExampleEvent - class to hold app event -AppExample - app class -app_cpp_example - function that launch app -*/ - -// event enumeration type -typedef uint8_t event_t; - -// app state class -class AppExampleState { -public: - // state data - uint8_t example_data; - - // state initializer - AppExampleState() { - example_data = 0; - } -}; - -// app events class -class AppExampleEvent { -public: - // events enum - static const event_t EventTypeTick = 0; - static const event_t EventTypeKey = 1; - - // payload - union { - InputEvent input; - } value; - - // event type - event_t type; -}; - -// our app derived from base AppTemplate class -// with template variables -class AppExample : public AppTemplate { -public: - uint8_t run(); - void render(Canvas* canvas); -}; - -// start app -uint8_t AppExample::run() { - // here we dont need to acquire or release state - // because before we call app_ready our application is "single threaded" - state.example_data = 12; - - // signal that we ready to render and ipc - app_ready(); - - // from here, any data that pass in render function must be guarded - // by calling acquire_state and release_state - - AppExampleEvent event; - while(1) { - if(get_event(&event, 1000)) { - if(event.type == AppExampleEvent::EventTypeKey) { - // press events - if(event.value.input.type == InputTypeShort && - event.value.input.key == InputKeyBack) { - printf("bye!\n"); - return exit(); - } - - if(event.value.input.type == InputTypeShort && - event.value.input.key == InputKeyUp) { - // to read or write state you need to execute - // acquire modify release state - acquire_state(); - state.example_data = 24; - release_state(); - } - } - } - - // signal to force gui update - update_gui(); - }; -} - -// render app -void AppExample::render(Canvas* canvas) { - // here you dont need to call acquire_state or release_state - // to read or write app state, that already handled by caller - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 2, state.example_data, "Example app"); -} - -// app enter function -extern "C" uint8_t app_cpp_example(void* p) { - AppExample* app = new AppExample(); - return app->run(); -} diff --git a/lib/app-template/app_template.h b/lib/app-template/app_template.h deleted file mode 100644 index 7adcf2bf..00000000 --- a/lib/app-template/app_template.h +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once - -#include "callback-connector.h" -#include -#include -#include -#include - -// simple app class with template variables -template class AppTemplate { -public: - ViewPort* view_port; - osMessageQueueId_t event_queue; - TState state; - ValueMutex state_mutex; - Gui* gui; - - AppTemplate(); - ~AppTemplate(); - void input_callback(InputEvent* input_event, void* ctx); - void draw_callback(Canvas* canvas, void* ctx); - virtual void render(Canvas* canvas) = 0; - void acquire_state(void); - void release_state(void); - bool get_event(TEvent* event, uint32_t timeout); - void app_ready(void); - uint8_t exit(void); - void update_gui(void); -}; - -template AppTemplate::AppTemplate() { - // allocate events queue - event_queue = osMessageQueueNew(10, sizeof(TEvent), NULL); - - // allocate valuemutex - // TODO: use plain os mutex? - if(!init_mutex(&state_mutex, &state, sizeof(TState))) { - printf("cannot create mutex\n"); - furi_crash(NULL); - } - - // open gui - gui = (Gui*)furi_record_open("gui"); - - // allocate view_port - view_port = view_port_alloc(); -} - -template AppTemplate::~AppTemplate() { -} - -// generic input callback -template -void AppTemplate::input_callback(InputEvent* input_event, void* ctx) { - AppTemplate* app = static_cast(ctx); - - TEvent event; - event.type = TEvent::EventTypeKey; - event.value.input = *input_event; - osMessageQueuePut(app->event_queue, &event, 0, 0); -} - -// generic draw callback -template -void AppTemplate::draw_callback(Canvas* canvas, void* ctx) { - AppTemplate* app = static_cast(ctx); - app->acquire_state(); - - canvas_clear(canvas); - app->render(canvas); - - app->release_state(); -} - -template void AppTemplate::acquire_state(void) { - acquire_mutex(&state_mutex, osWaitForever); -} - -template void AppTemplate::release_state(void) { - release_mutex(&state_mutex, &state); -} - -template -bool AppTemplate::get_event(TEvent* event, uint32_t timeout) { - osStatus_t event_status = osMessageQueueGet(event_queue, event, NULL, timeout); - - return (event_status == osOK); -} - -// signal that app is ready, and we can render something -// also unblock dependent tasks -template void AppTemplate::app_ready(void) { - // connect view_port with input callback - auto input_cb_ref = cbc::obtain_connector(this, &AppTemplate::input_callback); - view_port_input_callback_set(view_port, input_cb_ref, this); - - // connect view_port with draw callback - auto draw_cb_ref = cbc::obtain_connector(this, &AppTemplate::draw_callback); - view_port_draw_callback_set(view_port, draw_cb_ref, this); - - // add view_port - gui_add_view_port(gui, view_port, GuiLayerFullscreen); -} - -template uint8_t AppTemplate::exit(void) { - // TODO remove all view_ports create by app - view_port_enabled_set(view_port, false); - return 255; -} - -template void AppTemplate::update_gui(void) { - view_port_update(view_port); -} From 522420ec70a9c40e53afa90022e1763f47ce07d6 Mon Sep 17 00:00:00 2001 From: Kate Temkin Date: Mon, 23 May 2022 07:21:34 -0600 Subject: [PATCH 09/28] RFID: add support for Kantech IOProx cards (#1261) --- .../lfrfid/helpers/decoder_ioprox.cpp | 107 ++++++++++ applications/lfrfid/helpers/decoder_ioprox.h | 26 +++ .../lfrfid/helpers/encoder_ioprox.cpp | 32 +++ applications/lfrfid/helpers/encoder_ioprox.h | 25 +++ applications/lfrfid/helpers/key_info.cpp | 10 + applications/lfrfid/helpers/key_info.h | 1 + .../helpers/protocols/protocol_ioprox.cpp | 193 ++++++++++++++++++ .../helpers/protocols/protocol_ioprox.h | 26 +++ applications/lfrfid/helpers/rfid_reader.cpp | 7 + applications/lfrfid/helpers/rfid_reader.h | 2 + .../lfrfid/helpers/rfid_timer_emulator.h | 2 + applications/lfrfid/helpers/rfid_worker.cpp | 6 + applications/lfrfid/helpers/rfid_writer.cpp | 23 +++ applications/lfrfid/helpers/rfid_writer.h | 1 + applications/lfrfid/lfrfid_cli.cpp | 4 + .../scene/lfrfid_app_scene_delete_confirm.cpp | 8 + .../scene/lfrfid_app_scene_read_success.cpp | 40 +++- .../lfrfid/scene/lfrfid_app_scene_save_type.h | 2 +- .../scene/lfrfid_app_scene_saved_info.cpp | 8 + 19 files changed, 516 insertions(+), 7 deletions(-) create mode 100644 applications/lfrfid/helpers/decoder_ioprox.cpp create mode 100644 applications/lfrfid/helpers/decoder_ioprox.h create mode 100644 applications/lfrfid/helpers/encoder_ioprox.cpp create mode 100644 applications/lfrfid/helpers/encoder_ioprox.h create mode 100644 applications/lfrfid/helpers/protocols/protocol_ioprox.cpp create mode 100644 applications/lfrfid/helpers/protocols/protocol_ioprox.h diff --git a/applications/lfrfid/helpers/decoder_ioprox.cpp b/applications/lfrfid/helpers/decoder_ioprox.cpp new file mode 100644 index 00000000..7b44d3ce --- /dev/null +++ b/applications/lfrfid/helpers/decoder_ioprox.cpp @@ -0,0 +1,107 @@ +#include "decoder_ioprox.h" +#include +#include +#include + +constexpr uint32_t clocks_in_us = 64; + +constexpr uint32_t jitter_time_us = 20; +constexpr uint32_t min_time_us = 64; +constexpr uint32_t max_time_us = 80; +constexpr uint32_t baud_time_us = 500; + +constexpr uint32_t min_time = (min_time_us - jitter_time_us) * clocks_in_us; +constexpr uint32_t mid_time = ((max_time_us - min_time_us) / 2 + min_time_us) * clocks_in_us; +constexpr uint32_t max_time = (max_time_us + jitter_time_us) * clocks_in_us; +constexpr uint32_t baud_time = baud_time_us * clocks_in_us; + +bool DecoderIoProx::read(uint8_t* data, uint8_t data_size) { + bool result = false; + furi_assert(data_size >= 4); + + if(ready) { + result = true; + ioprox.decode(raw_data, sizeof(raw_data), data, data_size); + ready = false; + } + + return result; +} + +void DecoderIoProx::process_front(bool is_rising_edge, uint32_t time) { + if(ready) { + return; + } + + // Always track the time that's gone by. + current_period_duration += time; + demodulation_sample_duration += time; + + // If a baud time has elapsed, we're at a sample point. + if(demodulation_sample_duration >= baud_time) { + // Start a new baud period... + demodulation_sample_duration = 0; + demodulated_value_invalid = false; + + // ... and if we didn't have any baud errors, capture a sample. + if(!demodulated_value_invalid) { + store_data(current_demodulated_value); + } + } + + // + // FSK demodulator. + // + + // If this isn't a rising edge, this isn't a pulse of interest. + // We're done. + if(!is_rising_edge) { + return; + } + + bool is_valid_low = (current_period_duration > min_time) && + (current_period_duration <= mid_time); + bool is_valid_high = (current_period_duration > mid_time) && + (current_period_duration < max_time); + + // If this is between the minimum and our threshold, this is a logical 0. + if(is_valid_low) { + current_demodulated_value = false; + } + // Otherwise, if between our threshold and the max time, it's a logical 1. + else if(is_valid_high) { + current_demodulated_value = true; + } + // Otherwise, invalidate this sample. + else { + demodulated_value_invalid = true; + } + + // We're starting a new period; track that. + current_period_duration = 0; +} + +DecoderIoProx::DecoderIoProx() { + reset_state(); +} + +void DecoderIoProx::store_data(bool data) { + for(int i = 0; i < 7; ++i) { + raw_data[i] = (raw_data[i] << 1) | ((raw_data[i + 1] >> 7) & 1); + } + raw_data[7] = (raw_data[7] << 1) | data; + + if(ioprox.can_be_decoded(raw_data, sizeof(raw_data))) { + ready = true; + } +} + +void DecoderIoProx::reset_state() { + current_demodulated_value = false; + demodulated_value_invalid = false; + + current_period_duration = 0; + demodulation_sample_duration = 0; + + ready = false; +} diff --git a/applications/lfrfid/helpers/decoder_ioprox.h b/applications/lfrfid/helpers/decoder_ioprox.h new file mode 100644 index 00000000..aff4a477 --- /dev/null +++ b/applications/lfrfid/helpers/decoder_ioprox.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include "protocols/protocol_ioprox.h" + +class DecoderIoProx { +public: + bool read(uint8_t* data, uint8_t data_size); + void process_front(bool polarity, uint32_t time); + DecoderIoProx(); + +private: + uint32_t current_period_duration = 0; + uint32_t demodulation_sample_duration = 0; + + bool current_demodulated_value = false; + bool demodulated_value_invalid = false; + + uint8_t raw_data[8] = {0}; + void store_data(bool data); + + std::atomic ready; + + void reset_state(); + ProtocolIoProx ioprox; +}; diff --git a/applications/lfrfid/helpers/encoder_ioprox.cpp b/applications/lfrfid/helpers/encoder_ioprox.cpp new file mode 100644 index 00000000..41779918 --- /dev/null +++ b/applications/lfrfid/helpers/encoder_ioprox.cpp @@ -0,0 +1,32 @@ +#include "encoder_ioprox.h" +#include "protocols/protocol_ioprox.h" +#include + +void EncoderIoProx::init(const uint8_t* data, const uint8_t data_size) { + ProtocolIoProx ioprox; + ioprox.encode(data, data_size, card_data, sizeof(card_data)); + card_data_index = 0; +} + +void EncoderIoProx::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) { + uint8_t bit = (card_data[card_data_index / 8] >> (7 - (card_data_index % 8))) & 1; + + bool advance = fsk->next(bit, period); + if(advance) { + card_data_index++; + if(card_data_index >= (8 * card_data_max)) { + card_data_index = 0; + } + } + + *polarity = true; + *pulse = *period / 2; +} + +EncoderIoProx::EncoderIoProx() { + fsk = new OscFSK(8, 10, 64); +} + +EncoderIoProx::~EncoderIoProx() { + delete fsk; +} diff --git a/applications/lfrfid/helpers/encoder_ioprox.h b/applications/lfrfid/helpers/encoder_ioprox.h new file mode 100644 index 00000000..568b4067 --- /dev/null +++ b/applications/lfrfid/helpers/encoder_ioprox.h @@ -0,0 +1,25 @@ +#pragma once +#include "encoder_generic.h" +#include "osc_fsk.h" + +class EncoderIoProx : public EncoderGeneric { +public: + /** + * @brief init data to emulate + * + * @param data 1 byte FC, 1 byte Version, 2 bytes code + * @param data_size must be 4 + */ + void init(const uint8_t* data, const uint8_t data_size) final; + void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final; + EncoderIoProx(); + ~EncoderIoProx(); + +private: + static const uint8_t card_data_max = 8; + + uint8_t card_data[card_data_max]; + uint8_t card_data_index; + + OscFSK* fsk; +}; diff --git a/applications/lfrfid/helpers/key_info.cpp b/applications/lfrfid/helpers/key_info.cpp index ed2b20a9..4803cd6d 100644 --- a/applications/lfrfid/helpers/key_info.cpp +++ b/applications/lfrfid/helpers/key_info.cpp @@ -12,6 +12,9 @@ const char* lfrfid_key_get_type_string(LfrfidKeyType type) { case LfrfidKeyType::KeyI40134: return "I40134"; break; + case LfrfidKeyType::KeyIoProxXSF: + return "IoProxXSF"; + break; } return "Unknown"; @@ -28,6 +31,8 @@ const char* lfrfid_key_get_manufacturer_string(LfrfidKeyType type) { case LfrfidKeyType::KeyI40134: return "Indala"; break; + case LfrfidKeyType::KeyIoProxXSF: + return "Kantech"; } return "Unknown"; @@ -42,6 +47,8 @@ bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type) { *type = LfrfidKeyType::KeyH10301; } else if(strcmp("I40134", string) == 0) { *type = LfrfidKeyType::KeyI40134; + } else if(strcmp("IoProxXSF", string) == 0) { + *type = LfrfidKeyType::KeyIoProxXSF; } else { result = false; } @@ -60,6 +67,9 @@ uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type) { case LfrfidKeyType::KeyI40134: return 3; break; + case LfrfidKeyType::KeyIoProxXSF: + return 4; + break; } return 0; diff --git a/applications/lfrfid/helpers/key_info.h b/applications/lfrfid/helpers/key_info.h index e465011d..75a0a940 100644 --- a/applications/lfrfid/helpers/key_info.h +++ b/applications/lfrfid/helpers/key_info.h @@ -8,6 +8,7 @@ enum class LfrfidKeyType : uint8_t { KeyEM4100, KeyH10301, KeyI40134, + KeyIoProxXSF, }; const char* lfrfid_key_get_type_string(LfrfidKeyType type); diff --git a/applications/lfrfid/helpers/protocols/protocol_ioprox.cpp b/applications/lfrfid/helpers/protocols/protocol_ioprox.cpp new file mode 100644 index 00000000..b3b6a0e5 --- /dev/null +++ b/applications/lfrfid/helpers/protocols/protocol_ioprox.cpp @@ -0,0 +1,193 @@ +#include "protocol_ioprox.h" +#include +#include + +/** + * Writes a bit into the output buffer. + */ +static void write_bit(bool bit, uint8_t position, uint8_t* data) { + if(bit) { + data[position / 8] |= 1UL << (7 - (position % 8)); + } else { + data[position / 8] &= ~(1UL << (7 - (position % 8))); + } +} + +/** + * Writes up to eight contiguous bits into the output buffer. + */ +static void write_bits(uint8_t byte, uint8_t position, uint8_t* data, uint8_t length) { + furi_check(length <= 8); + furi_check(length > 0); + + for(uint8_t i = 0; i < length; ++i) { + uint8_t shift = 7 - i; + write_bit((byte >> shift) & 1, position + i, data); + } +} + +uint8_t ProtocolIoProx::get_encoded_data_size() { + return 8; +} + +uint8_t ProtocolIoProx::get_decoded_data_size() { + return 4; +} + +void ProtocolIoProx::encode( + const uint8_t* decoded_data, + const uint8_t decoded_data_size, + uint8_t* encoded_data, + const uint8_t encoded_data_size) { + furi_check(decoded_data_size >= get_decoded_data_size()); + furi_check(encoded_data_size >= get_encoded_data_size()); + + // Packet to transmit: + // + // 0 10 20 30 40 50 60 + // v v v v v v v + // 01234567 8 90123456 7 89012345 6 78901234 5 67890123 4 56789012 3 45678901 23 + // ----------------------------------------------------------------------------- + // 00000000 0 11110000 1 facility 1 version_ 1 code-one 1 code-two 1 checksum 11 + + // Preamble. + write_bits(0b00000000, 0, encoded_data, 8); + write_bit(0, 8, encoded_data); + + write_bits(0b11110000, 9, encoded_data, 8); + write_bit(1, 17, encoded_data); + + // Facility code. + write_bits(decoded_data[0], 18, encoded_data, 8); + write_bit(1, 26, encoded_data); + + // Version + write_bits(decoded_data[1], 27, encoded_data, 8); + write_bit(1, 35, encoded_data); + + // Code one + write_bits(decoded_data[2], 36, encoded_data, 8); + write_bit(1, 44, encoded_data); + + // Code two + write_bits(decoded_data[3], 45, encoded_data, 8); + write_bit(1, 53, encoded_data); + + // Checksum + write_bits(compute_checksum(encoded_data, 8), 54, encoded_data, 8); + write_bit(1, 62, encoded_data); + write_bit(1, 63, encoded_data); +} + +void ProtocolIoProx::decode( + const uint8_t* encoded_data, + const uint8_t encoded_data_size, + uint8_t* decoded_data, + const uint8_t decoded_data_size) { + furi_check(decoded_data_size >= get_decoded_data_size()); + furi_check(encoded_data_size >= get_encoded_data_size()); + + // Packet structure: + // (Note: the second word seems fixed; but this may not be a guarantee; + // it currently has no meaning.) + // + //0 1 2 3 4 5 6 7 + //v v v v v v v v + //01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF + //----------------------------------------------------------------------- + //00000000 01111000 01FFFFFF FF1VVVVV VVV1CCCC CCCC1CCC CCCCC1XX XXXXXX11 + // + // F = facility code + // V = version + // C = code + // X = checksum + + // Facility code + decoded_data[0] = (encoded_data[2] << 2) | (encoded_data[3] >> 6); + + // Version code. + decoded_data[1] = (encoded_data[3] << 3) | (encoded_data[4] >> 5); + + // Code bytes. + decoded_data[2] = (encoded_data[4] << 4) | (encoded_data[5] >> 4); + decoded_data[3] = (encoded_data[5] << 5) | (encoded_data[6] >> 3); +} + +bool ProtocolIoProx::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) { + furi_check(encoded_data_size >= get_encoded_data_size()); + + // Packet framing + // + //0 1 2 3 4 5 6 7 + //v v v v v v v v + //01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF + //----------------------------------------------------------------------- + //00000000 01______ _1______ __1_____ ___1____ ____1___ _____1XX XXXXXX11 + // + // _ = variable data + // 0 = preamble 0 + // 1 = framing 1 + // X = checksum + + // Validate the packet preamble is there... + if(encoded_data[0] != 0b00000000) { + return false; + } + if((encoded_data[1] >> 6) != 0b01) { + return false; + } + + // ... check for known ones... + if((encoded_data[2] & 0b01000000) == 0) { + return false; + } + if((encoded_data[3] & 0b00100000) == 0) { + return false; + } + if((encoded_data[4] & 0b00010000) == 0) { + return false; + } + if((encoded_data[5] & 0b00001000) == 0) { + return false; + } + if((encoded_data[6] & 0b00000100) == 0) { + return false; + } + if((encoded_data[7] & 0b00000011) == 0) { + return false; + } + + // ... and validate our checksums. + uint8_t checksum = compute_checksum(encoded_data, 8); + uint8_t checkval = (encoded_data[6] << 6) | (encoded_data[7] >> 2); + + if(checksum != checkval) { + return false; + } + + return true; +} + +uint8_t ProtocolIoProx::compute_checksum(const uint8_t* data, const uint8_t data_size) { + furi_check(data_size == get_encoded_data_size()); + + // Packet structure: + // + //0 1 2 3 4 5 6 7 + //v v v v v v v v + //01234567 8 9ABCDEF0 1 23456789 A BCDEF012 3 456789AB C DEF01234 5 6789ABCD EF + //00000000 0 VVVVVVVV 1 WWWWWWWW 1 XXXXXXXX 1 YYYYYYYY 1 ZZZZZZZZ 1 CHECKSUM 11 + // + // algorithm as observed by the proxmark3 folks + // CHECKSUM == 0xFF - (V + W + X + Y + Z) + + uint8_t checksum = 0; + + checksum += (data[1] << 1) | (data[2] >> 7); // VVVVVVVVV + checksum += (data[2] << 2) | (data[3] >> 6); // WWWWWWWWW + checksum += (data[3] << 3) | (data[4] >> 5); // XXXXXXXXX + checksum += (data[4] << 4) | (data[5] >> 4); // YYYYYYYYY + checksum += (data[5] << 5) | (data[6] >> 3); // ZZZZZZZZZ + + return 0xFF - checksum; +} diff --git a/applications/lfrfid/helpers/protocols/protocol_ioprox.h b/applications/lfrfid/helpers/protocols/protocol_ioprox.h new file mode 100644 index 00000000..2fff53b1 --- /dev/null +++ b/applications/lfrfid/helpers/protocols/protocol_ioprox.h @@ -0,0 +1,26 @@ +#pragma once +#include "protocol_generic.h" + +class ProtocolIoProx : public ProtocolGeneric { +public: + uint8_t get_encoded_data_size() final; + uint8_t get_decoded_data_size() final; + + void encode( + const uint8_t* decoded_data, + const uint8_t decoded_data_size, + uint8_t* encoded_data, + const uint8_t encoded_data_size) final; + + void decode( + const uint8_t* encoded_data, + const uint8_t encoded_data_size, + uint8_t* decoded_data, + const uint8_t decoded_data_size) final; + + bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final; + +private: + /** Computes the IoProx checksum of the provided (decoded) data. */ + uint8_t compute_checksum(const uint8_t* data, const uint8_t data_size); +}; diff --git a/applications/lfrfid/helpers/rfid_reader.cpp b/applications/lfrfid/helpers/rfid_reader.cpp index 1a6ed5f7..fb837cb7 100644 --- a/applications/lfrfid/helpers/rfid_reader.cpp +++ b/applications/lfrfid/helpers/rfid_reader.cpp @@ -25,10 +25,12 @@ void RfidReader::decode(bool polarity) { case Type::Normal: decoder_em.process_front(polarity, period); decoder_hid26.process_front(polarity, period); + decoder_ioprox.process_front(polarity, period); break; case Type::Indala: decoder_em.process_front(polarity, period); decoder_hid26.process_front(polarity, period); + decoder_ioprox.process_front(polarity, period); decoder_indala.process_front(polarity, period); break; } @@ -110,6 +112,11 @@ bool RfidReader::read(LfrfidKeyType* _type, uint8_t* data, uint8_t data_size, bo something_read = true; } + if(decoder_ioprox.read(data, data_size)) { + *_type = LfrfidKeyType::KeyIoProxXSF; + something_read = true; + } + if(decoder_indala.read(data, data_size)) { *_type = LfrfidKeyType::KeyI40134; something_read = true; diff --git a/applications/lfrfid/helpers/rfid_reader.h b/applications/lfrfid/helpers/rfid_reader.h index b3a93b89..903bbecf 100644 --- a/applications/lfrfid/helpers/rfid_reader.h +++ b/applications/lfrfid/helpers/rfid_reader.h @@ -4,6 +4,7 @@ #include "decoder_emmarin.h" #include "decoder_hid26.h" #include "decoder_indala.h" +#include "decoder_ioprox.h" #include "key_info.h" //#define RFID_GPIO_DEBUG 1 @@ -34,6 +35,7 @@ private: DecoderEMMarin decoder_em; DecoderHID26 decoder_hid26; DecoderIndala decoder_indala; + DecoderIoProx decoder_ioprox; uint32_t last_dwt_value; diff --git a/applications/lfrfid/helpers/rfid_timer_emulator.h b/applications/lfrfid/helpers/rfid_timer_emulator.h index 874a4c3d..2129a1c7 100644 --- a/applications/lfrfid/helpers/rfid_timer_emulator.h +++ b/applications/lfrfid/helpers/rfid_timer_emulator.h @@ -5,6 +5,7 @@ #include "encoder_emmarin.h" #include "encoder_hid_h10301.h" #include "encoder_indala_40134.h" +#include "encoder_ioprox.h" #include "pulse_joiner.h" #include @@ -22,6 +23,7 @@ private: {LfrfidKeyType::KeyEM4100, new EncoderEM()}, {LfrfidKeyType::KeyH10301, new EncoderHID_H10301()}, {LfrfidKeyType::KeyI40134, new EncoderIndala_40134()}, + {LfrfidKeyType::KeyIoProxXSF, new EncoderIoProx()}, }; PulseJoiner pulse_joiner; diff --git a/applications/lfrfid/helpers/rfid_worker.cpp b/applications/lfrfid/helpers/rfid_worker.cpp index 6e023cfb..af15a340 100644 --- a/applications/lfrfid/helpers/rfid_worker.cpp +++ b/applications/lfrfid/helpers/rfid_worker.cpp @@ -84,6 +84,11 @@ void RfidWorker::sq_write() { writer.write_indala(key.get_data()); writer.stop(); break; + case LfrfidKeyType::KeyIoProxXSF: + writer.start(); + writer.write_ioprox(key.get_data()); + writer.stop(); + break; } } } @@ -92,6 +97,7 @@ void RfidWorker::sq_write_start_validate() { switch(key.get_type()) { case LfrfidKeyType::KeyEM4100: case LfrfidKeyType::KeyH10301: + case LfrfidKeyType::KeyIoProxXSF: reader.start_forced(RfidReader::Type::Normal); break; case LfrfidKeyType::KeyI40134: diff --git a/applications/lfrfid/helpers/rfid_writer.cpp b/applications/lfrfid/helpers/rfid_writer.cpp index 996cb36a..e85ab936 100644 --- a/applications/lfrfid/helpers/rfid_writer.cpp +++ b/applications/lfrfid/helpers/rfid_writer.cpp @@ -1,4 +1,5 @@ #include "rfid_writer.h" +#include "protocols/protocol_ioprox.h" #include #include "protocols/protocol_emmarin.h" #include "protocols/protocol_hid_h10301.h" @@ -143,6 +144,28 @@ void RfidWriter::write_hid(const uint8_t hid_data[3]) { FURI_CRITICAL_EXIT(); } +/** Endian fixup. Translates an ioprox block into a t5577 block */ +static uint32_t ioprox_encode_block(const uint8_t block_data[4]) { + uint8_t raw_card_data[] = {block_data[3], block_data[2], block_data[1], block_data[0]}; + return *reinterpret_cast(&raw_card_data); +} + +void RfidWriter::write_ioprox(const uint8_t ioprox_data[4]) { + ProtocolIoProx ioprox_card; + + uint8_t encoded_data[8]; + ioprox_card.encode(ioprox_data, 4, encoded_data, sizeof(encoded_data)); + + const uint32_t ioprox_config_block_data = 0b00000000000101000111000001000000; + + FURI_CRITICAL_ENTER(); + write_block(0, 0, false, ioprox_config_block_data); + write_block(0, 1, false, ioprox_encode_block(&encoded_data[0])); + write_block(0, 2, false, ioprox_encode_block(&encoded_data[4])); + write_reset(); + FURI_CRITICAL_EXIT(); +} + void RfidWriter::write_indala(const uint8_t indala_data[3]) { ProtocolIndala40134 indala_card; uint32_t card_data[2]; diff --git a/applications/lfrfid/helpers/rfid_writer.h b/applications/lfrfid/helpers/rfid_writer.h index 38329877..98d2bf95 100644 --- a/applications/lfrfid/helpers/rfid_writer.h +++ b/applications/lfrfid/helpers/rfid_writer.h @@ -9,6 +9,7 @@ public: void stop(); void write_em(const uint8_t em_data[5]); void write_hid(const uint8_t hid_data[3]); + void write_ioprox(const uint8_t ioprox_data[4]); void write_indala(const uint8_t indala_data[3]); private: diff --git a/applications/lfrfid/lfrfid_cli.cpp b/applications/lfrfid/lfrfid_cli.cpp index dbd37ddc..451f10ce 100644 --- a/applications/lfrfid/lfrfid_cli.cpp +++ b/applications/lfrfid/lfrfid_cli.cpp @@ -28,6 +28,7 @@ void lfrfid_cli_print_usage() { printf("\tEM4100, EM-Marin (5 bytes key_data)\r\n"); printf("\tH10301, HID26 (3 bytes key_data)\r\n"); printf("\tI40134, Indala (3 bytes key_data)\r\n"); + printf("\tIoProxXSF, IoProx (4 bytes key_data)\r\n"); printf("\t are hex-formatted\r\n"); }; @@ -43,6 +44,9 @@ static bool lfrfid_cli_get_key_type(string_t data, LfrfidKeyType* type) { } else if(string_cmp_str(data, "I40134") == 0 || string_cmp_str(data, "Indala") == 0) { result = true; *type = LfrfidKeyType::KeyI40134; + } else if(string_cmp_str(data, "IoProxXSF") == 0 || string_cmp_str(data, "IoProx") == 0) { + result = true; + *type = LfrfidKeyType::KeyIoProxXSF; } return result; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp b/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp index 40bd9e36..236ca8c2 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp @@ -50,6 +50,14 @@ void LfRfidAppSceneDeleteConfirm::on_enter(LfRfidApp* app, bool /* need_restore string_printf( string_decrypted, "FC: %u ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2]))); break; + case LfrfidKeyType::KeyIoProxXSF: + string_printf( + string_decrypted, + "FC: %u VC: %u ID: %u", + data[0], + data[1], + (uint16_t)((data[2] << 8) | (data[3]))); + break; } line_3->set_text( string_get_cstr(string_decrypted), 64, 39, 0, AlignCenter, AlignBottom, FontSecondary); diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp b/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp index 2b81a58a..fd6f46a0 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp @@ -7,6 +7,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ string_init(string[0]); string_init(string[1]); string_init(string[2]); + string_init(string[3]); auto container = app->view_controller.get(); @@ -25,11 +26,13 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ header->set_text(app->worker.key.get_type_text(), 89, 3, 0, AlignCenter); auto line_1_text = container->add(); - auto line_2_text = container->add(); + auto line_2l_text = container->add(); + auto line_2r_text = container->add(); auto line_3_text = container->add(); auto line_1_value = container->add(); - auto line_2_value = container->add(); + auto line_2l_value = container->add(); + auto line_2r_value = container->add(); auto line_3_value = container->add(); const uint8_t* data = app->worker.key.get_data(); @@ -37,7 +40,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ switch(app->worker.key.get_type()) { case LfrfidKeyType::KeyEM4100: line_1_text->set_text("HEX:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary); - line_2_text->set_text("Mod:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); + line_2l_text->set_text("Mod:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); line_3_text->set_text("ID:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary); for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { @@ -49,7 +52,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ line_1_value->set_text( string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary); - line_2_value->set_text( + line_2l_value->set_text( string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary); line_3_value->set_text( string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary); @@ -57,7 +60,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ case LfrfidKeyType::KeyH10301: case LfrfidKeyType::KeyI40134: line_1_text->set_text("HEX:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary); - line_2_text->set_text("FC:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); + line_2l_text->set_text("FC:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); line_3_text->set_text("Card:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary); for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { @@ -69,11 +72,36 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ line_1_value->set_text( string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary); - line_2_value->set_text( + line_2l_value->set_text( string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary); line_3_value->set_text( string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary); break; + + case LfrfidKeyType::KeyIoProxXSF: + line_1_text->set_text("HEX:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary); + line_2l_text->set_text("FC:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); + line_2r_text->set_text("V:", 95, 35, 0, AlignRight, AlignBottom, FontSecondary); + line_3_text->set_text("Card:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary); + + for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { + string_cat_printf(string[0], "%02X", data[i]); + } + + string_printf(string[1], "%u", data[0]); + string_printf(string[2], "%u", (uint16_t)((data[2] << 8) | (data[3]))); + string_printf(string[3], "%u", data[1]); + + line_1_value->set_text( + string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary); + line_2l_value->set_text( + string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary); + line_2r_value->set_text( + string_get_cstr(string[3]), 98, 35, 0, AlignLeft, AlignBottom, FontSecondary); + line_3_value->set_text( + string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary); + + break; } app->view_controller.switch_to(); diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_type.h b/applications/lfrfid/scene/lfrfid_app_scene_save_type.h index 1f6f6d74..847c0dab 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_type.h +++ b/applications/lfrfid/scene/lfrfid_app_scene_save_type.h @@ -10,6 +10,6 @@ public: private: static void submenu_callback(void* context, uint32_t index); uint32_t submenu_item_selected = 0; - static const uint8_t keys_count = static_cast(LfrfidKeyType::KeyI40134); + static const uint8_t keys_count = static_cast(LfrfidKeyType::KeyIoProxXSF); string_t submenu_name[keys_count + 1]; }; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp b/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp index 73c9a403..dd4a3d4e 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp @@ -43,6 +43,14 @@ void LfRfidAppSceneSavedInfo::on_enter(LfRfidApp* app, bool /* need_restore */) string_printf( string_decrypted, "FC: %u ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2]))); break; + case LfrfidKeyType::KeyIoProxXSF: + string_printf( + string_decrypted, + "FC: %u VC: %u ID: %u", + data[0], + data[1], + (uint16_t)((data[2] << 8) | (data[3]))); + break; } line_3->set_text( string_get_cstr(string_decrypted), 64, 39, 0, AlignCenter, AlignBottom, FontSecondary); From 0b0ca597ea125b7f302a34e4a95af0ea88f14889 Mon Sep 17 00:00:00 2001 From: Gary Date: Mon, 23 May 2022 13:58:13 -0400 Subject: [PATCH 10/28] Rework NFC EMV response parsing. Split TLV and tags per EMV spec. (#1257) * Rework NFC EMV response parsing. Split TLV and tags per EMV spec. * Requested changes: fb -> first_byte and missed printf to FURI_LOG_T Co-authored-by: Gary Co-authored-by: gornekich --- lib/nfc_protocols/emv.c | 211 +++++++++++++++++++++------------------- 1 file changed, 113 insertions(+), 98 deletions(-) diff --git a/lib/nfc_protocols/emv.c b/lib/nfc_protocols/emv.c index 8e14cf48..416e63a5 100644 --- a/lib/nfc_protocols/emv.c +++ b/lib/nfc_protocols/emv.c @@ -10,6 +10,9 @@ const PDOLValue pdol_merchant_type = {0x9F58, {0x01}}; // Merchant type indicato const PDOLValue pdol_term_trans_qualifies = { 0x9F66, {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers +const PDOLValue pdol_addtnl_term_qualifies = { + 0x9F40, + {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers const PDOLValue pdol_amount_authorise = { 0x9F02, {0x00, 0x00, 0x00, 0x10, 0x00, 0x00}}; // Amount, authorised @@ -30,6 +33,7 @@ const PDOLValue* const pdol_values[] = { &pdol_term_type, &pdol_merchant_type, &pdol_term_trans_qualifies, + &pdol_addtnl_term_qualifies, &pdol_amount_authorise, &pdol_amount, &pdol_country_code, @@ -61,6 +65,11 @@ static const uint8_t pdol_ans[] = {0x77, 0x40, 0x82, 0x02, 0x20, 0x00, 0x57, 0x1 static void emv_trace(FuriHalNfcTxRxContext* tx_rx, const char* message) { if(furi_log_get_level() == FuriLogLevelTrace) { FURI_LOG_T(TAG, "%s", message); + printf("TX: "); + for(size_t i = 0; i < tx_rx->tx_bits / 8; i++) { + printf("%02X ", tx_rx->tx_data[i]); + } + printf("\r\nRX: "); for(size_t i = 0; i < tx_rx->rx_bits / 8; i++) { printf("%02X ", tx_rx->rx_data[i]); } @@ -68,42 +77,109 @@ static void emv_trace(FuriHalNfcTxRxContext* tx_rx, const char* message) { } } -static uint16_t emv_parse_TLV(uint8_t* dest, uint8_t* src, uint16_t* idx) { - uint8_t len = src[*idx + 1]; - memcpy(dest, &src[*idx + 2], len); - *idx = *idx + len + 1; - return len; -} - -static bool emv_decode_search_tag_u16_r(uint16_t tag, uint8_t* buff, uint16_t* idx) { - if((buff[*idx] << 8 | buff[*idx + 1]) == tag) { - *idx = *idx + 3; - return true; - } - return false; -} - -bool emv_decode_ppse_response(uint8_t* buff, uint16_t len, EmvApplication* app) { +static bool emv_decode_response(uint8_t* buff, uint16_t len, EmvApplication* app) { uint16_t i = 0; - bool app_aid_found = false; + uint16_t tag = 0, first_byte = 0; + uint16_t tlen = 0; + bool success = false; while(i < len) { - if(buff[i] == EMV_TAG_APP_TEMPLATE) { - uint8_t app_len = buff[++i]; - for(uint16_t j = i; j < MIN(i + app_len, len - 1); j++) { - if(buff[j] == EMV_TAG_AID) { - app_aid_found = true; - app->aid_len = buff[j + 1]; - emv_parse_TLV(app->aid, buff, &j); - } else if(buff[j] == EMV_TAG_PRIORITY) { - emv_parse_TLV(&app->priority, buff, &j); - } - } - i += app_len; + first_byte = buff[i]; + if((first_byte & 31) == 31) { // 2-byte tag + tag = buff[i] << 8 | buff[i + 1]; + i++; + FURI_LOG_T(TAG, " 2-byte TLV EMV tag: %x", tag); + } else { + tag = buff[i]; + FURI_LOG_T(TAG, " 1-byte TLV EMV tag: %x", tag); } i++; + tlen = buff[i]; + if((tlen & 128) == 128) { // long length value + i++; + tlen = buff[i]; + FURI_LOG_T(TAG, " 2-byte TLV length: %d", tlen); + } else { + FURI_LOG_T(TAG, " 1-byte TLV length: %d", tlen); + } + i++; + if((first_byte & 32) == 32) { // "Constructed" -- contains more TLV data to parse + FURI_LOG_T(TAG, "Constructed TLV %x", tag); + if(!emv_decode_response(&buff[i], tlen, app)) { + FURI_LOG_T(TAG, "Failed to decode response for %x", tag); + // return false; + } else { + success = true; + } + } else { + switch(tag) { + case EMV_TAG_AID: + app->aid_len = tlen; + memcpy(app->aid, &buff[i], tlen); + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_AID %x", tag); + break; + case EMV_TAG_PRIORITY: + memcpy(&app->priority, &buff[i], tlen); + success = true; + break; + case EMV_TAG_CARD_NAME: + memcpy(app->name, &buff[i], tlen); + app->name[tlen] = '\0'; + app->name_found = true; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name); + break; + case EMV_TAG_PDOL: + memcpy(app->pdol.data, &buff[i], tlen); + app->pdol.size = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_PDOL %x (len=%d)", tag, tlen); + break; + case EMV_TAG_AFL: + memcpy(app->afl.data, &buff[i], tlen); + app->afl.size = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen); + break; + case EMV_TAG_CARD_NUM: // Track 2 Equivalent Data. 0xD0 delimits PAN from expiry (YYMM) + for(int x = 1; x < tlen; x++) { + if(buff[i + x + 1] > 0xD0) { + memcpy(app->card_number, &buff[i], x + 1); + app->card_number_len = x + 1; + break; + } + } + success = true; + FURI_LOG_T( + TAG, + "found EMV_TAG_CARD_NUM %x (len=%d)", + EMV_TAG_CARD_NUM, + app->card_number_len); + break; + case EMV_TAG_PAN: + memcpy(app->card_number, &buff[i], tlen); + app->card_number_len = tlen; + success = true; + break; + case EMV_TAG_EXP_DATE: + app->exp_year = buff[i]; + app->exp_month = buff[i + 1]; + success = true; + break; + case EMV_TAG_CURRENCY_CODE: + app->currency_code = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + case EMV_TAG_COUNTRY_CODE: + app->country_code = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + } + } + i += tlen; } - return app_aid_found; + return success; } bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { @@ -124,7 +200,7 @@ bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { FURI_LOG_D(TAG, "Send select PPSE"); if(furi_hal_nfc_tx_rx(tx_rx, 300)) { emv_trace(tx_rx, "Select PPSE answer:"); - if(emv_decode_ppse_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { app_aid_found = true; } else { FURI_LOG_E(TAG, "Failed to parse application"); @@ -136,28 +212,6 @@ bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { return app_aid_found; } -static bool emv_decode_select_app_response(uint8_t* buff, uint16_t len, EmvApplication* app) { - uint16_t i = 0; - bool decode_success = false; - - while(i < len) { - if(buff[i] == EMV_TAG_CARD_NAME) { - uint8_t name_len = buff[i + 1]; - emv_parse_TLV((uint8_t*)app->name, buff, &i); - app->name[name_len] = '\0'; - app->name_found = true; - decode_success = true; - } else if(((buff[i] << 8) | buff[i + 1]) == EMV_TAG_PDOL) { - i++; - app->pdol.size = emv_parse_TLV(app->pdol.data, buff, &i); - decode_success = true; - } - i++; - } - - return decode_success; -} - bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { bool select_app_success = false; const uint8_t emv_select_header[] = { @@ -181,7 +235,7 @@ bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { FURI_LOG_D(TAG, "Start application"); if(furi_hal_nfc_tx_rx(tx_rx, 300)) { emv_trace(tx_rx, "Start application answer:"); - if(emv_decode_select_app_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { select_app_success = true; } else { FURI_LOG_E(TAG, "Failed to read PAN or PDOL"); @@ -226,22 +280,6 @@ static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) { return dest->size; } -static bool emv_decode_get_proc_opt(uint8_t* buff, uint16_t len, EmvApplication* app) { - bool card_num_read = false; - - for(uint16_t i = 0; i < len; i++) { - if(buff[i] == EMV_TAG_CARD_NUM) { - app->card_number_len = 8; - memcpy(app->card_number, &buff[i + 2], app->card_number_len); - card_num_read = true; - } else if(buff[i] == EMV_TAG_AFL) { - app->afl.size = emv_parse_TLV(app->afl.data, buff, &i); - } - } - - return card_num_read; -} - static bool emv_get_processing_options(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { bool card_num_read = false; const uint8_t emv_gpo_header[] = {0x80, 0xA8, 0x00, 0x00}; @@ -264,8 +302,10 @@ static bool emv_get_processing_options(FuriHalNfcTxRxContext* tx_rx, EmvApplicat FURI_LOG_D(TAG, "Get proccessing options"); if(furi_hal_nfc_tx_rx(tx_rx, 300)) { emv_trace(tx_rx, "Get processing options answer:"); - if(emv_decode_get_proc_opt(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { - card_num_read = true; + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + if(app->card_number_len > 0) { + card_num_read = true; + } } } else { FURI_LOG_E(TAG, "Failed to get processing options"); @@ -274,31 +314,6 @@ static bool emv_get_processing_options(FuriHalNfcTxRxContext* tx_rx, EmvApplicat return card_num_read; } -static bool emv_decode_read_sfi_record(uint8_t* buff, uint16_t len, EmvApplication* app) { - bool pan_parsed = false; - - for(uint16_t i = 0; i < len; i++) { - if(buff[i] == EMV_TAG_PAN) { - if(buff[i + 1] == 8 || buff[i + 1] == 10) { - app->card_number_len = buff[i + 1]; - memcpy(app->card_number, &buff[i + 2], app->card_number_len); - pan_parsed = true; - } - } else if(emv_decode_search_tag_u16_r(EMV_TAG_EXP_DATE, buff, &i)) { - app->exp_year = buff[i++]; - app->exp_month = buff[i++]; - } else if(emv_decode_search_tag_u16_r(EMV_TAG_CURRENCY_CODE, buff, &i)) { - app->currency_code = (buff[i] << 8) | buff[i + 1]; - i += 2; - } else if(emv_decode_search_tag_u16_r(EMV_TAG_COUNTRY_CODE, buff, &i)) { - app->country_code = (buff[i] << 8) | buff[i + 1]; - i += 1; - } - } - - return pan_parsed; -} - static bool emv_read_sfi_record( FuriHalNfcTxRxContext* tx_rx, EmvApplication* app, @@ -320,7 +335,7 @@ static bool emv_read_sfi_record( if(furi_hal_nfc_tx_rx(tx_rx, 300)) { emv_trace(tx_rx, "SFI record:"); - if(emv_decode_read_sfi_record(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { card_num_read = true; } } else { From 5f7ee1b3605c55cc2e99a53c2cf9634333011fb9 Mon Sep 17 00:00:00 2001 From: Samuel Yvon Date: Mon, 23 May 2022 14:19:41 -0400 Subject: [PATCH 11/28] Add instruction for missing deps. (#1265) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- ReadMe.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index c6b73c8b..9e34148f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -7,6 +7,13 @@ Welcome to [Flipper Zero](https://flipperzero.one/)'s Firmware repo! Our goal is to create nice and clean code with good documentation, to make it a pleasure for everyone to work with. +# Clone the Repository + +You should clone with +```shell +$ git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git +``` + # Update firmware [Get Latest Firmware from Update Server](https://update.flipperzero.one/) @@ -74,6 +81,8 @@ Check `dist/` for build outputs. Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device. +If compilation fails, make sure all submodules are all initialized. Either clone with `--recursive` or use `git submodule update --init --recursive`. + # Build on Linux/macOS ## macOS Prerequisites From ca5c0d2d502040b2d662706da0b88bbd0c51cff1 Mon Sep 17 00:00:00 2001 From: Gabe Chai Date: Mon, 23 May 2022 16:50:11 -0400 Subject: [PATCH 12/28] Adding FIDO U2F to aid.nfc (#1251) * Adding FIDO U2F to aid.nfc * Run make -C assets all --- assets/resources/Manifest | 4 ++-- assets/resources/nfc/assets/aid.nfc | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/resources/Manifest b/assets/resources/Manifest index 08d95044..76b90232 100644 --- a/assets/resources/Manifest +++ b/assets/resources/Manifest @@ -1,5 +1,5 @@ V:0 -T:1651524332 +T:1653334495 D:badusb D:dolphin D:infrared @@ -226,7 +226,7 @@ D:infrared/assets F:d895fda2f48c6cc4c55e8a398ff52e43:74300:infrared/assets/tv.ir F:a157a80f5a668700403d870c23b9567d:470:music_player/Marble_Machine.fmf D:nfc/assets -F:c6826a621d081d68309e4be424d3d974:4715:nfc/assets/aid.nfc +F:81dc04c7b181f94b644079a71476dff4:4742:nfc/assets/aid.nfc F:86efbebdf41bb6bf15cc51ef88f069d5:2565:nfc/assets/country_code.nfc F:41b4f08774249014cb8d3dffa5f5c07d:1757:nfc/assets/currency_code.nfc F:c60e862919731b0bd538a1001bbc1098:17453:nfc/assets/mf_classic_dict.nfc diff --git a/assets/resources/nfc/assets/aid.nfc b/assets/resources/nfc/assets/aid.nfc index 43854f3d..30c1030e 100644 --- a/assets/resources/nfc/assets/aid.nfc +++ b/assets/resources/nfc/assets/aid.nfc @@ -64,6 +64,7 @@ A0000004540010: Etranzact Genesis Card A0000004540011: Etranzact Genesis Card 2 A0000004766C: GOOGLE_PAYMENT A0000005241010: RuPay +A0000006472F0001: FIDO U2F A0000006723010: TROY chip credit card A0000006723020: TROY chip debit card A0000007705850: XTRAPOWER From eb83b0f02abc70330d1309d4bd5197eaad0bb242 Mon Sep 17 00:00:00 2001 From: Samuel Yvon Date: Mon, 23 May 2022 16:57:49 -0400 Subject: [PATCH 13/28] (Typo) Conrol=>Control (#1266) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix typo (Conrol=>Control) Co-authored-by: あく --- applications/gpio/gpio_custom_event.h | 2 +- applications/gpio/scenes/gpio_scene_start.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/gpio/gpio_custom_event.h b/applications/gpio/gpio_custom_event.h index cae36035..2bf3e5a8 100644 --- a/applications/gpio/gpio_custom_event.h +++ b/applications/gpio/gpio_custom_event.h @@ -3,7 +3,7 @@ typedef enum { GpioStartEventOtgOff = 0, GpioStartEventOtgOn, - GpioStartEventManualConrol, + GpioStartEventManualControl, GpioStartEventUsbUart, GpioCustomEventErrorBack, diff --git a/applications/gpio/scenes/gpio_scene_start.c b/applications/gpio/scenes/gpio_scene_start.c index 4df74114..41b74523 100644 --- a/applications/gpio/scenes/gpio_scene_start.c +++ b/applications/gpio/scenes/gpio_scene_start.c @@ -23,7 +23,7 @@ static void gpio_scene_start_var_list_enter_callback(void* context, uint32_t ind furi_assert(context); GpioApp* app = context; if(index == GpioItemTest) { - view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventManualConrol); + view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventManualControl); } else if(index == GpioItemUsbUart) { view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventUsbUart); } @@ -82,7 +82,7 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { furi_hal_power_enable_otg(); } else if(event.event == GpioStartEventOtgOff) { furi_hal_power_disable_otg(); - } else if(event.event == GpioStartEventManualConrol) { + } else if(event.event == GpioStartEventManualControl) { scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemTest); scene_manager_next_scene(app->scene_manager, GpioSceneTest); } else if(event.event == GpioStartEventUsbUart) { From f90c9320d951533f0b661f5f7a48697bfd369191 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 23 May 2022 15:43:39 -0600 Subject: [PATCH 14/28] bt: Fix race condition when disconnect during TX (#1260) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bt: Fix race condition when disconnect during TX * bt: Move flag clear to not be in middle of other logic * bt: Bail out of send bytes a little bit earlier Co-authored-by: gornekich Co-authored-by: あく --- applications/bt/bt_service/bt.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) mode change 100755 => 100644 applications/bt/bt_service/bt.c diff --git a/applications/bt/bt_service/bt.c b/applications/bt/bt_service/bt.c old mode 100755 new mode 100644 index 4a7b6f65..4ea5d735 --- a/applications/bt/bt_service/bt.c +++ b/applications/bt/bt_service/bt.c @@ -167,7 +167,11 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt furi_assert(context); Bt* bt = context; - osEventFlagsClear(bt->rpc_event, BT_RPC_EVENT_ALL); + if(osEventFlagsGet(bt->rpc_event) & BT_RPC_EVENT_DISCONNECTED) { + // Early stop from sending if we're already disconnected + return; + } + osEventFlagsClear(bt->rpc_event, BT_RPC_EVENT_ALL & (~BT_RPC_EVENT_DISCONNECTED)); size_t bytes_sent = 0; while(bytes_sent < bytes_len) { size_t bytes_remain = bytes_len - bytes_sent; @@ -178,10 +182,14 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt furi_hal_bt_serial_tx(&bytes[bytes_sent], bytes_remain); bytes_sent += bytes_remain; } - uint32_t event_flag = - osEventFlagsWait(bt->rpc_event, BT_RPC_EVENT_ALL, osFlagsWaitAny, osWaitForever); + // We want BT_RPC_EVENT_DISCONNECTED to stick, so don't clear + uint32_t event_flag = osEventFlagsWait( + bt->rpc_event, BT_RPC_EVENT_ALL, osFlagsWaitAny | osFlagsNoClear, osWaitForever); if(event_flag & BT_RPC_EVENT_DISCONNECTED) { break; + } else { + // If we didn't get BT_RPC_EVENT_DISCONNECTED, then clear everything else + osEventFlagsClear(bt->rpc_event, BT_RPC_EVENT_ALL & (~BT_RPC_EVENT_DISCONNECTED)); } } } @@ -197,6 +205,8 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { bt->status = BtStatusConnected; BtMessage message = {.type = BtMessageTypeUpdateStatus}; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); + // Clear BT_RPC_EVENT_DISCONNECTED because it might be set from previous session + osEventFlagsClear(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); if(bt->profile == BtProfileSerial) { // Open RPC session bt->rpc_session = rpc_session_open(bt->rpc); From 2017baac48939fc58344a2d60294a9d9e6513b64 Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 24 May 2022 16:42:02 +0300 Subject: [PATCH 15/28] [FL-2441] BLE add Power state, fix double connection (#1238) * battery service: add power state charachteristic * bt: update power state on charging / discharging events * ble config: support only one connection * bt: always update flow control characteristic * bt: fix power state update * bt: simplify updating power state * bt: don't update flow control charachteristic --- applications/bt/bt_service/bt.c | 13 ++- applications/bt/bt_service/bt_i.h | 1 + firmware/targets/f7/ble_glue/app_conf.h | 2 +- .../targets/f7/ble_glue/battery_service.c | 99 +++++++++++++++++-- .../targets/f7/ble_glue/battery_service.h | 2 + firmware/targets/f7/furi_hal/furi_hal_bt.c | 6 ++ .../targets/furi_hal_include/furi_hal_bt.h | 3 + 7 files changed, 115 insertions(+), 11 deletions(-) diff --git a/applications/bt/bt_service/bt.c b/applications/bt/bt_service/bt.c index 4ea5d735..38aadc49 100644 --- a/applications/bt/bt_service/bt.c +++ b/applications/bt/bt_service/bt.c @@ -91,11 +91,16 @@ static void bt_battery_level_changed_callback(const void* _event, void* context) furi_assert(context); Bt* bt = context; + BtMessage message = {}; const PowerEvent* event = _event; if(event->type == PowerEventTypeBatteryLevelChanged) { - BtMessage message = { - .type = BtMessageTypeUpdateBatteryLevel, - .data.battery_level = event->data.battery_level}; + message.type = BtMessageTypeUpdateBatteryLevel; + message.data.battery_level = event->data.battery_level; + furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); + } else if( + event->type == PowerEventTypeStartCharging || event->type == PowerEventTypeFullyCharged || + event->type == PowerEventTypeStopCharging) { + message.type = BtMessageTypeUpdatePowerState; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); } } @@ -378,6 +383,8 @@ int32_t bt_srv() { } else if(message.type == BtMessageTypeUpdateBatteryLevel) { // Update battery level furi_hal_bt_update_battery_level(message.data.battery_level); + } else if(message.type == BtMessageTypeUpdatePowerState) { + furi_hal_bt_update_power_state(); } else if(message.type == BtMessageTypePinCodeShow) { // Display PIN code bt_pin_code_show(bt, message.data.pin_code); diff --git a/applications/bt/bt_service/bt_i.h b/applications/bt/bt_service/bt_i.h index 610c0905..caa45360 100644 --- a/applications/bt/bt_service/bt_i.h +++ b/applications/bt/bt_service/bt_i.h @@ -21,6 +21,7 @@ typedef enum { BtMessageTypeUpdateStatus, BtMessageTypeUpdateBatteryLevel, + BtMessageTypeUpdatePowerState, BtMessageTypePinCodeShow, BtMessageTypeKeysStorageUpdated, BtMessageTypeSetProfile, diff --git a/firmware/targets/f7/ble_glue/app_conf.h b/firmware/targets/f7/ble_glue/app_conf.h index 1406f964..1d7474da 100644 --- a/firmware/targets/f7/ble_glue/app_conf.h +++ b/firmware/targets/f7/ble_glue/app_conf.h @@ -127,7 +127,7 @@ * Maximum number of simultaneous connections that the device will support. * Valid values are from 1 to 8 */ -#define CFG_BLE_NUM_LINK 2 +#define CFG_BLE_NUM_LINK 1 /** * Maximum number of Services that can be stored in the GATT database. diff --git a/firmware/targets/f7/ble_glue/battery_service.c b/firmware/targets/f7/ble_glue/battery_service.c index 85f1eeea..a95f9187 100644 --- a/firmware/targets/f7/ble_glue/battery_service.c +++ b/firmware/targets/f7/ble_glue/battery_service.c @@ -3,18 +3,50 @@ #include "ble.h" #include +#include #define TAG "BtBatterySvc" typedef struct { uint16_t svc_handle; - uint16_t char_level_handle; + uint16_t battery_level_char_handle; + uint16_t power_state_char_handle; } BatterySvc; +enum { + // Common states + BatterySvcPowerStateUnknown = 0b00, + BatterySvcPowerStateUnsupported = 0b01, + // Level states + BatterySvcPowerStateGoodLevel = 0b10, + BatterySvcPowerStateCriticallyLowLevel = 0b11, + // Charging states + BatterySvcPowerStateNotCharging = 0b10, + BatterySvcPowerStateCharging = 0b11, + // Discharging states + BatterySvcPowerStateNotDischarging = 0b10, + BatterySvcPowerStateDischarging = 0b11, + // Battery states + BatterySvcPowerStateBatteryNotPresent = 0b10, + BatterySvcPowerStateBatteryPresent = 0b11, +}; + +typedef struct { + uint8_t present : 2; + uint8_t discharging : 2; + uint8_t charging : 2; + uint8_t level : 2; +} BattrySvcPowerState; + +_Static_assert(sizeof(BattrySvcPowerState) == 1, "Incorrect structure size"); + static BatterySvc* battery_svc = NULL; +#define BATTERY_POWER_STATE (0x2A1A) + static const uint16_t service_uuid = BATTERY_SERVICE_UUID; -static const uint16_t char_battery_level_uuid = BATTERY_LEVEL_CHAR_UUID; +static const uint16_t battery_level_char_uuid = BATTERY_LEVEL_CHAR_UUID; +static const uint16_t power_state_char_uuid = BATTERY_POWER_STATE; void battery_svc_start() { battery_svc = malloc(sizeof(BatterySvc)); @@ -22,7 +54,7 @@ void battery_svc_start() { // Add Battery service status = aci_gatt_add_service( - UUID_TYPE_16, (Service_UUID_t*)&service_uuid, PRIMARY_SERVICE, 4, &battery_svc->svc_handle); + UUID_TYPE_16, (Service_UUID_t*)&service_uuid, PRIMARY_SERVICE, 8, &battery_svc->svc_handle); if(status) { FURI_LOG_E(TAG, "Failed to add Battery service: %d", status); } @@ -30,24 +62,47 @@ void battery_svc_start() { status = aci_gatt_add_char( battery_svc->svc_handle, UUID_TYPE_16, - (Char_UUID_t*)&char_battery_level_uuid, + (Char_UUID_t*)&battery_level_char_uuid, 1, CHAR_PROP_READ | CHAR_PROP_NOTIFY, ATTR_PERMISSION_AUTHEN_READ, GATT_DONT_NOTIFY_EVENTS, 10, CHAR_VALUE_LEN_CONSTANT, - &battery_svc->char_level_handle); + &battery_svc->battery_level_char_handle); if(status) { FURI_LOG_E(TAG, "Failed to add Battery level characteristic: %d", status); } + // Add Power state characteristic + status = aci_gatt_add_char( + battery_svc->svc_handle, + UUID_TYPE_16, + (Char_UUID_t*)&power_state_char_uuid, + 1, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_AUTHEN_READ, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_CONSTANT, + &battery_svc->power_state_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add Battery level characteristic: %d", status); + } + // Update power state charachteristic + battery_svc_update_power_state(); } void battery_svc_stop() { tBleStatus status; if(battery_svc) { // Delete Battery level characteristic - status = aci_gatt_del_char(battery_svc->svc_handle, battery_svc->char_level_handle); + status = + aci_gatt_del_char(battery_svc->svc_handle, battery_svc->battery_level_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Battery level characteristic: %d", status); + } + // Delete Power state characteristic + status = aci_gatt_del_char(battery_svc->svc_handle, battery_svc->power_state_char_handle); if(status) { FURI_LOG_E(TAG, "Failed to delete Battery level characteristic: %d", status); } @@ -73,9 +128,39 @@ bool battery_svc_update_level(uint8_t battery_charge) { // Update battery level characteristic FURI_LOG_D(TAG, "Updating battery level characteristic"); tBleStatus result = aci_gatt_update_char_value( - battery_svc->svc_handle, battery_svc->char_level_handle, 0, 1, &battery_charge); + battery_svc->svc_handle, battery_svc->battery_level_char_handle, 0, 1, &battery_charge); if(result) { FURI_LOG_E(TAG, "Failed updating RX characteristic: %d", result); } return result != BLE_STATUS_SUCCESS; } + +bool battery_svc_update_power_state() { + // Check if service was started + if(battery_svc == NULL) { + return false; + } + // Update power state characteristic + BattrySvcPowerState power_state = { + .level = BatterySvcPowerStateUnsupported, + .present = BatterySvcPowerStateBatteryPresent, + }; + if(furi_hal_power_is_charging()) { + power_state.charging = BatterySvcPowerStateCharging; + power_state.discharging = BatterySvcPowerStateNotDischarging; + } else { + power_state.charging = BatterySvcPowerStateNotCharging; + power_state.discharging = BatterySvcPowerStateDischarging; + } + FURI_LOG_D(TAG, "Updating power state characteristic"); + tBleStatus result = aci_gatt_update_char_value( + battery_svc->svc_handle, + battery_svc->power_state_char_handle, + 0, + 1, + (uint8_t*)&power_state); + if(result) { + FURI_LOG_E(TAG, "Failed updating Power state characteristic: %d", result); + } + return result != BLE_STATUS_SUCCESS; +} diff --git a/firmware/targets/f7/ble_glue/battery_service.h b/firmware/targets/f7/ble_glue/battery_service.h index 2d35e252..f38bfc00 100644 --- a/firmware/targets/f7/ble_glue/battery_service.h +++ b/firmware/targets/f7/ble_glue/battery_service.h @@ -15,6 +15,8 @@ bool battery_svc_is_started(); bool battery_svc_update_level(uint8_t battery_level); +bool battery_svc_update_power_state(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 48d69844..1e869077 100755 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -284,6 +284,12 @@ void furi_hal_bt_update_battery_level(uint8_t battery_level) { } } +void furi_hal_bt_update_power_state() { + if(battery_svc_is_started()) { + battery_svc_update_power_state(); + } +} + void furi_hal_bt_get_key_storage_buff(uint8_t** key_buff_addr, uint16_t* key_buff_size) { ble_app_get_key_storage_buff(key_buff_addr, key_buff_size); } diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h index 73b16bec..3a05081b 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt.h @@ -91,6 +91,9 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, */ void furi_hal_bt_update_battery_level(uint8_t battery_level); +/** Update battery power state */ +void furi_hal_bt_update_power_state(); + /** Checks if BLE state is active * * @return true if device is connected or advertising, false otherwise From d31578508a95144cf5964878349a21aac642850d Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 24 May 2022 17:00:15 +0300 Subject: [PATCH 16/28] [FL-2245] Introduce Mifare Classic Emulation (#1242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * digital signal: introduce digital signal * nfca: add nfca signal encoder * nfc: add mifare classic emulation scene * nfca: add classic emulation support to lib and hal * mifare classic: support basic read commands * nfc: add mifare classic menu scene * mifare classic: start parsing commands in emulation * mifare classic: add nested auth * nfc: fix errors * mifare classic: add encrypt function * nfc: fix mifare classic save * lib hex: add hex uint64_t ASCII parser * flipper format: add uint64 hex format support * nfc: add mifare classic key map * nfc: hide mifare classic keys on emulation * mifare classic: add NACK responce * nfc: add partial bytes support in transparent mode * nfc: mifare classic add shadow file support * digital signal: move arr buffer from BSS to heap * mifare classic: process access bits more careful * nfca: fix memory leack * nfc: format sources * mifare classic: cleun up Co-authored-by: あく --- applications/nfc/nfc.c | 2 + applications/nfc/nfc_device.c | 29 +- applications/nfc/nfc_worker.c | 31 ++ applications/nfc/nfc_worker.h | 1 + applications/nfc/scenes/nfc_scene_config.h | 2 + .../scenes/nfc_scene_emulate_mifare_classic.c | 64 +++ .../scenes/nfc_scene_mifare_classic_menu.c | 64 +++ .../scenes/nfc_scene_read_mifare_classic.c | 3 +- .../nfc/scenes/nfc_scene_saved_menu.c | 12 +- applications/nfc/views/dict_attack.c | 2 +- core/furi/common_defines.h | 2 +- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 89 +++- .../targets/furi_hal_include/furi_hal_nfc.h | 5 + lib/digital_signal/digital_signal.c | 173 +++++++ lib/digital_signal/digital_signal.h | 29 ++ lib/flipper_format/flipper_format.c | 31 ++ lib/flipper_format/flipper_format.h | 28 ++ lib/flipper_format/flipper_format_stream.c | 13 + lib/flipper_format/flipper_format_stream.h | 1 + lib/lib.mk | 5 + lib/nfc_protocols/crypto1.c | 2 +- lib/nfc_protocols/crypto1.h | 2 +- lib/nfc_protocols/mifare_classic.c | 437 +++++++++++++++++- lib/nfc_protocols/mifare_classic.h | 12 + lib/nfc_protocols/nfca.c | 84 ++++ lib/nfc_protocols/nfca.h | 14 + lib/toolbox/hex.c | 11 + lib/toolbox/hex.h | 30 +- 28 files changed, 1150 insertions(+), 28 deletions(-) create mode 100644 applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c create mode 100644 applications/nfc/scenes/nfc_scene_mifare_classic_menu.c create mode 100644 lib/digital_signal/digital_signal.c create mode 100644 lib/digital_signal/digital_signal.h diff --git a/applications/nfc/nfc.c b/applications/nfc/nfc.c index 4660b344..c4eaf900 100755 --- a/applications/nfc/nfc.c +++ b/applications/nfc/nfc.c @@ -173,6 +173,8 @@ int32_t nfc_app(void* p) { if(nfc_device_load(nfc->dev, p)) { if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl); + } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); } diff --git a/applications/nfc/nfc_device.c b/applications/nfc/nfc_device.c index 9970cbfe..0771a1ef 100644 --- a/applications/nfc/nfc_device.c +++ b/applications/nfc/nfc_device.c @@ -7,6 +7,9 @@ static const char* nfc_file_header = "Flipper NFC device"; static const uint32_t nfc_file_version = 2; +// Protocols format versions +static const uint32_t nfc_mifare_classic_data_format_version = 1; + NfcDevice* nfc_device_alloc() { NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); nfc_dev->storage = furi_record_open("storage"); @@ -624,6 +627,7 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* // Save Mifare Classic specific data do { if(!flipper_format_write_comment_cstr(file, "Mifare Classic specific data")) break; + if(data->type == MfClassicType1k) { if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break; blocks = 64; @@ -631,8 +635,17 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break; blocks = 256; } - if(!flipper_format_write_comment_cstr(file, "Mifare Classic blocks")) break; + if(!flipper_format_write_uint32( + file, "Data format version", &nfc_mifare_classic_data_format_version, 1)) + break; + if(!flipper_format_write_comment_cstr( + file, "Key map is the bit mask indicating valid key in each sector")) + break; + if(!flipper_format_write_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break; + if(!flipper_format_write_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break; + + if(!flipper_format_write_comment_cstr(file, "Mifare Classic blocks")) break; bool block_saved = true; for(size_t i = 0; i < blocks; i++) { string_printf(temp_str, "Block %d", i); @@ -654,6 +667,7 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* bool parsed = false; MfClassicData* data = &dev->dev_data.mf_classic_data; string_t temp_str; + uint32_t data_format_version = 0; string_init(temp_str); uint16_t data_blocks = 0; @@ -669,6 +683,19 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* } else { break; } + + // Read Mifare Classic format version + if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) { + // Load unread sectors with zero keys access for backward compatability + if(!flipper_format_rewind(file)) break; + data->key_a_mask = 0xffffffffffffffff; + data->key_b_mask = 0xffffffffffffffff; + } else { + if(data_format_version != nfc_mifare_classic_data_format_version) break; + if(!flipper_format_read_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break; + if(!flipper_format_read_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break; + } + // Read Mifare Classic blocks bool block_read = true; for(size_t i = 0; i < data_blocks; i++) { diff --git a/applications/nfc/nfc_worker.c b/applications/nfc/nfc_worker.c index 6b3c8f09..5ae99d6d 100644 --- a/applications/nfc/nfc_worker.c +++ b/applications/nfc/nfc_worker.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "helpers/nfc_mf_classic_dict.h" @@ -104,6 +105,8 @@ int32_t nfc_worker_task(void* context) { nfc_worker_emulate_mifare_ul(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateReadMifareClassic) { nfc_worker_mifare_classic_dict_attack(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) { + nfc_worker_emulate_mifare_classic(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateReadMifareDesfire) { nfc_worker_read_mifare_desfire(nfc_worker); } @@ -474,6 +477,34 @@ void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker) { stream_free(nfc_worker->dict_stream); } +void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + MfClassicEmulator emulator = { + .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), + .data = nfc_worker->dev_data->mf_classic_data, + .data_changed = false, + }; + NfcaSignal* nfca_signal = nfca_signal_alloc(); + tx_rx.nfca_signal = nfca_signal; + + while(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) { + if(furi_hal_nfc_listen( + nfc_data->uid, nfc_data->uid_len, nfc_data->atqa, nfc_data->sak, true, 300)) { + mf_classic_emulator(&emulator, &tx_rx); + } + } + if(emulator.data_changed) { + nfc_worker->dev_data->mf_classic_data = emulator.data; + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); + } + emulator.data_changed = false; + } + + nfca_signal_free(nfca_signal); +} + void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { ReturnCode err; uint8_t tx_buff[64] = {}; diff --git a/applications/nfc/nfc_worker.h b/applications/nfc/nfc_worker.h index 1933a79b..a68f42d7 100755 --- a/applications/nfc/nfc_worker.h +++ b/applications/nfc/nfc_worker.h @@ -19,6 +19,7 @@ typedef enum { NfcWorkerStateReadMifareUltralight, NfcWorkerStateEmulateMifareUltralight, NfcWorkerStateReadMifareClassic, + NfcWorkerStateEmulateMifareClassic, NfcWorkerStateReadMifareDesfire, // Transition NfcWorkerStateStop, diff --git a/applications/nfc/scenes/nfc_scene_config.h b/applications/nfc/scenes/nfc_scene_config.h index 6b5d5d10..e6351d42 100755 --- a/applications/nfc/scenes/nfc_scene_config.h +++ b/applications/nfc/scenes/nfc_scene_config.h @@ -34,4 +34,6 @@ ADD_SCENE(nfc, restore_original, RestoreOriginal) ADD_SCENE(nfc, debug, Debug) ADD_SCENE(nfc, field, Field) ADD_SCENE(nfc, read_mifare_classic, ReadMifareClassic) +ADD_SCENE(nfc, emulate_mifare_classic, EmulateMifareClassic) +ADD_SCENE(nfc, mifare_classic_menu, MifareClassicMenu) ADD_SCENE(nfc, dict_not_found, DictNotFound) diff --git a/applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c b/applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c new file mode 100644 index 00000000..1286024c --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c @@ -0,0 +1,64 @@ +#include "../nfc_i.h" +#include + +#define NFC_MF_CLASSIC_DATA_NOT_CHANGED (0UL) +#define NFC_MF_CLASSIC_DATA_CHANGED (1UL) + +void nfc_emulate_mifare_classic_worker_callback(NfcWorkerEvent event, void* context) { + UNUSED(event); + Nfc* nfc = context; + + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneEmulateMifareClassic, NFC_MF_CLASSIC_DATA_CHANGED); +} + +void nfc_scene_emulate_mifare_classic_on_enter(void* context) { + Nfc* nfc = context; + DOLPHIN_DEED(DolphinDeedNfcEmulate); + + // Setup view + Popup* popup = nfc->popup; + if(strcmp(nfc->dev->dev_name, "")) { + nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); + } + popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); + popup_set_header(popup, "Emulating\nMf Classic", 56, 31, AlignLeft, AlignTop); + + // Setup and start worker + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + nfc_worker_start( + nfc->worker, + NfcWorkerStateEmulateMifareClassic, + &nfc->dev->dev_data, + nfc_emulate_mifare_classic_worker_callback, + nfc); +} + +bool nfc_scene_emulate_mifare_classic_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeTick) { + notification_message(nfc->notifications, &sequence_blink_blue_10); + consumed = true; + } else if(event.type == SceneManagerEventTypeBack) { + // Stop worker + nfc_worker_stop(nfc->worker); + // Check if data changed and save in shadow file + if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateMifareClassic) == + NFC_MF_CLASSIC_DATA_CHANGED) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneEmulateMifareClassic, NFC_MF_CLASSIC_DATA_NOT_CHANGED); + nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); + } + consumed = false; + } + return consumed; +} + +void nfc_scene_emulate_mifare_classic_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + popup_reset(nfc->popup); +} diff --git a/applications/nfc/scenes/nfc_scene_mifare_classic_menu.c b/applications/nfc/scenes/nfc_scene_mifare_classic_menu.c new file mode 100644 index 00000000..4611d16b --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_mifare_classic_menu.c @@ -0,0 +1,64 @@ +#include "../nfc_i.h" + +enum SubmenuIndex { + SubmenuIndexSave, + SubmenuIndexEmulate, +}; + +void nfc_scene_mifare_classic_menu_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_mifare_classic_menu_on_enter(void* context) { + Nfc* nfc = context; + Submenu* submenu = nfc->submenu; + + submenu_add_item( + submenu, "Save", SubmenuIndexSave, nfc_scene_mifare_classic_menu_submenu_callback, nfc); + submenu_add_item( + submenu, + "Emulate", + SubmenuIndexEmulate, + nfc_scene_mifare_classic_menu_submenu_callback, + nfc); + submenu_set_selected_item( + nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareUlMenu)); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mifare_classic_menu_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSave) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexSave); + nfc->dev->format = NfcDeviceSaveFormatMifareClassic; + // Clear device name + nfc_device_set_name(nfc->dev, ""); + scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); + consumed = true; + } else if(event.event == SubmenuIndexEmulate) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexEmulate); + scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = + scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); + } + + return consumed; +} + +void nfc_scene_mifare_classic_menu_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + submenu_reset(nfc->submenu); +} diff --git a/applications/nfc/scenes/nfc_scene_read_mifare_classic.c b/applications/nfc/scenes/nfc_scene_read_mifare_classic.c index c6ce4c2d..c4422285 100644 --- a/applications/nfc/scenes/nfc_scene_read_mifare_classic.c +++ b/applications/nfc/scenes/nfc_scene_read_mifare_classic.c @@ -47,7 +47,7 @@ bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent eve consumed = true; } else if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventDictAttackDone) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); + scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareClassicMenu); consumed = true; } else if(event.event == NfcWorkerEventDetectedClassic1k) { dict_attack_card_detected(nfc->dict_attack, MfClassicType1k); @@ -71,7 +71,6 @@ bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent eve scene_manager_set_scene_state( nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone); notification_message(nfc->notifications, &sequence_success); - nfc->dev->format = NfcDeviceSaveFormatMifareClassic; dict_attack_set_result(nfc->dict_attack, true); consumed = true; } else if(event.event == NfcWorkerEventFail) { diff --git a/applications/nfc/scenes/nfc_scene_saved_menu.c b/applications/nfc/scenes/nfc_scene_saved_menu.c index e0489c15..1b435ccd 100644 --- a/applications/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/nfc/scenes/nfc_scene_saved_menu.c @@ -27,13 +27,11 @@ void nfc_scene_saved_menu_on_enter(void* context) { SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); - } else if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { + } else if( + nfc->dev->format == NfcDeviceSaveFormatMifareUl || + nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { submenu_add_item( - submenu, - "Emulate Ultralight", - SubmenuIndexEmulate, - nfc_scene_saved_menu_submenu_callback, - nfc); + submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); } submenu_add_item( submenu, "Edit UID and Name", SubmenuIndexEdit, nfc_scene_saved_menu_submenu_callback, nfc); @@ -64,6 +62,8 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexEmulate) { if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl); + } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); } diff --git a/applications/nfc/views/dict_attack.c b/applications/nfc/views/dict_attack.c index 83a56b0b..0f9da494 100644 --- a/applications/nfc/views/dict_attack.c +++ b/applications/nfc/views/dict_attack.c @@ -46,7 +46,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, draw_str); } else if(m->state == DictAttackStateSuccess) { canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Complete!"); - elements_button_right(canvas, "Save"); + elements_button_right(canvas, "More"); } else if(m->state == DictAttackStateFail) { canvas_draw_str_aligned( canvas, 64, 2, AlignCenter, AlignTop, "Failed to read any sector"); diff --git a/core/furi/common_defines.h b/core/furi/common_defines.h index 5b0e6971..5e9a7904 100644 --- a/core/furi/common_defines.h +++ b/core/furi/common_defines.h @@ -84,7 +84,7 @@ extern "C" { #endif #ifndef FURI_BIT -#define FURI_BIT(x, n) ((x) >> (n)&1) +#define FURI_BIT(x, n) (((x) >> (n)) & 1) #endif #ifndef FURI_IS_IRQ_MASKED diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index 768a4bac..4391685e 100755 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -1,9 +1,12 @@ #include "furi_hal_nfc.h" #include +#include #include #include #include -#include + +#include +#include #define TAG "FuriHalNfc" @@ -394,6 +397,80 @@ ReturnCode furi_hal_nfc_data_exchange( return ret; } +static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { + furi_assert(tx_rx->nfca_signal); + + platformDisableIrqCallback(); + + bool ret = false; + + // Start transparent mode + st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE); + // Reconfigure gpio + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); + furi_hal_gpio_init(&gpio_spi_r_sck, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_init(&gpio_spi_r_miso, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_init(&gpio_nfc_cs, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&gpio_spi_r_mosi, false); + + // Send signal + nfca_signal_encode(tx_rx->nfca_signal, tx_rx->tx_data, tx_rx->tx_bits, tx_rx->tx_parity); + digital_signal_send(tx_rx->nfca_signal->tx_signal, &gpio_spi_r_mosi); + furi_hal_gpio_write(&gpio_spi_r_mosi, false); + + // Configure gpio back to SPI and exit transparent + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); + st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); + + // Manually wait for interrupt + furi_hal_gpio_init(&gpio_rfid_pull, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh); + st25r3916ClearAndEnableInterrupts(ST25R3916_IRQ_MASK_RXE); + + uint32_t irq = 0; + uint8_t rxe = 0; + uint32_t start = DWT->CYCCNT; + while(true) { + if(furi_hal_gpio_read(&gpio_rfid_pull) == true) { + st25r3916ReadRegister(ST25R3916_REG_IRQ_MAIN, &rxe); + if(rxe & (1 << 4)) { + irq = 1; + break; + } + } + uint32_t timeout = DWT->CYCCNT - start; + if(timeout / furi_hal_delay_instructions_per_microsecond() > timeout_ms * 1000) { + FURI_LOG_D(TAG, "Interrupt waiting timeout"); + break; + } + } + if(irq) { + uint8_t fifo_stat[2]; + st25r3916ReadMultipleRegisters( + ST25R3916_REG_FIFO_STATUS1, fifo_stat, ST25R3916_FIFO_STATUS_LEN); + uint16_t len = + ((((uint16_t)fifo_stat[1] & ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >> + ST25R3916_REG_FIFO_STATUS2_fifo_b_shift) + << RFAL_BITS_IN_BYTE); + len |= (((uint16_t)fifo_stat[0]) & 0x00FFU); + uint8_t rx[100]; + st25r3916ReadFifo(rx, len); + + tx_rx->rx_bits = len * 8; + memcpy(tx_rx->rx_data, rx, len); + + ret = true; + } else { + FURI_LOG_E(TAG, "Timeout error"); + ret = false; + } + + st25r3916ClearInterrupts(); + platformEnableIrqCallback(); + + return ret; +} + static uint32_t furi_hal_nfc_tx_rx_get_flag(FuriHalNfcTxRxType type) { uint32_t flags = 0; @@ -405,6 +482,9 @@ static uint32_t furi_hal_nfc_tx_rx_get_flag(FuriHalNfcTxRxType type) { } else if(type == FuriHalNfcTxRxTypeRaw) { flags = RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_CRC_RX_KEEP | RFAL_TXRX_FLAGS_PAR_RX_KEEP | RFAL_TXRX_FLAGS_PAR_TX_NONE; + } else if(type == FuriHalNfcTxRxTypeRxRaw) { + flags = RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_CRC_RX_KEEP | + RFAL_TXRX_FLAGS_PAR_RX_KEEP | RFAL_TXRX_FLAGS_PAR_TX_NONE; } return flags; @@ -470,6 +550,10 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { uint8_t* temp_rx_buff = NULL; uint16_t* temp_rx_bits = NULL; + if(tx_rx->tx_rx_type == FuriHalNfcTxRxTransparent) { + return furi_hal_nfc_transparent_tx_rx(tx_rx, timeout_ms); + } + // Prepare data for FIFO if necessary uint32_t flags = furi_hal_nfc_tx_rx_get_flag(tx_rx->tx_rx_type); if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw) { @@ -502,7 +586,8 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { osDelay(1); } - if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw) { + if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw || + tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRxRaw) { tx_rx->rx_bits = 8 * furi_hal_nfc_bitstream_to_data_and_parity( temp_rx_buff, *temp_rx_bits, tx_rx->rx_data, tx_rx->rx_parity); } else { diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h index 20a46900..860db80d 100755 --- a/firmware/targets/furi_hal_include/furi_hal_nfc.h +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -10,6 +10,8 @@ #include #include +#include + #ifdef __cplusplus extern "C" { #endif @@ -39,6 +41,8 @@ typedef enum { FuriHalNfcTxRxTypeRxNoCrc, FuriHalNfcTxRxTypeRxKeepPar, FuriHalNfcTxRxTypeRaw, + FuriHalNfcTxRxTypeRxRaw, + FuriHalNfcTxRxTransparent, } FuriHalNfcTxRxType; typedef bool (*FuriHalNfcEmulateCallback)( @@ -80,6 +84,7 @@ typedef struct { uint8_t rx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE]; uint16_t rx_bits; FuriHalNfcTxRxType tx_rx_type; + NfcaSignal* nfca_signal; } FuriHalNfcTxRxContext; /** Init nfc diff --git a/lib/digital_signal/digital_signal.c b/lib/digital_signal/digital_signal.c new file mode 100644 index 00000000..23ddaf90 --- /dev/null +++ b/lib/digital_signal/digital_signal.c @@ -0,0 +1,173 @@ +#include "digital_signal.h" + +#include +#include +#include +#include + +#define F_TIM (64000000.0) +#define T_TIM (1.0 / F_TIM) + +DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt) { + DigitalSignal* signal = malloc(sizeof(DigitalSignal)); + signal->start_level = true; + signal->edges_max_cnt = max_edges_cnt; + signal->edge_timings = malloc(max_edges_cnt * sizeof(float)); + signal->reload_reg_buff = malloc(max_edges_cnt * sizeof(uint32_t)); + signal->edge_cnt = 0; + + return signal; +} + +void digital_signal_free(DigitalSignal* signal) { + furi_assert(signal); + + free(signal->edge_timings); + free(signal->reload_reg_buff); + free(signal); +} + +bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b) { + furi_assert(signal_a); + furi_assert(signal_b); + + if(signal_a->edges_max_cnt < signal_a->edge_cnt + signal_b->edge_cnt) { + return false; + } + + bool end_level = signal_a->start_level; + if(signal_a->edge_cnt) { + end_level = signal_a->start_level ^ !(signal_a->edge_cnt % 2); + } + uint8_t start_copy = 0; + if(end_level == signal_b->start_level) { + if(signal_a->edge_cnt) { + signal_a->edge_timings[signal_a->edge_cnt - 1] += signal_b->edge_timings[0]; + start_copy += 1; + } else { + signal_a->edge_timings[signal_a->edge_cnt] += signal_b->edge_timings[0]; + } + } + memcpy( + &signal_a->edge_timings[signal_a->edge_cnt], + &signal_b->edge_timings[start_copy], + (signal_b->edge_cnt - start_copy) * sizeof(float)); + signal_a->edge_cnt += signal_b->edge_cnt - start_copy; + + return true; +} + +bool digital_signal_get_start_level(DigitalSignal* signal) { + furi_assert(signal); + + return signal->start_level; +} + +uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal) { + furi_assert(signal); + + return signal->edge_cnt; +} + +float digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num) { + furi_assert(signal); + furi_assert(edge_num < signal->edge_cnt); + + return signal->edge_timings[edge_num]; +} + +static void digital_signal_prepare_arr(DigitalSignal* signal) { + float t_signal = 0; + float t_current = 0; + float r = 0; + float r_int = 0; + float r_dec = 0; + + for(size_t i = 0; i < signal->edge_cnt - 1; i++) { + t_signal += signal->edge_timings[i]; + r = (t_signal - t_current) / T_TIM; + r_dec = modff(r, &r_int); + if(r_dec < 0.5f) { + signal->reload_reg_buff[i] = (uint32_t)r_int - 1; + } else { + signal->reload_reg_buff[i] = (uint32_t)r_int; + } + t_current += (signal->reload_reg_buff[i] + 1) * T_TIM; + } +} + +bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) { + furi_assert(signal); + furi_assert(gpio); + + // Configure gpio as output + furi_hal_gpio_init(gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + + // Init gpio buffer and DMA channel + uint16_t gpio_reg = gpio->port->ODR; + uint16_t gpio_buff[2]; + if(signal->start_level) { + gpio_buff[0] = gpio_reg | gpio->pin; + gpio_buff[1] = gpio_reg & ~(gpio->pin); + } else { + gpio_buff[0] = gpio_reg & ~(gpio->pin); + gpio_buff[1] = gpio_reg | gpio->pin; + } + LL_DMA_InitTypeDef dma_config = {}; + dma_config.MemoryOrM2MDstAddress = (uint32_t)gpio_buff; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->ODR); + dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + dma_config.Mode = LL_DMA_MODE_CIRCULAR; + dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD; + dma_config.NbData = 2; + dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); + LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, 2); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); + + // Init timer arr register buffer and DMA channel + digital_signal_prepare_arr(signal); + dma_config.MemoryOrM2MDstAddress = (uint32_t)signal->reload_reg_buff; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR); + dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + dma_config.Mode = LL_DMA_MODE_NORMAL; + dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + dma_config.NbData = signal->edge_cnt - 2; + dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + dma_config.Priority = LL_DMA_PRIORITY_HIGH; + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); + LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, signal->edge_cnt - 2); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + + // Set up timer + LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetPrescaler(TIM2, 0); + LL_TIM_SetAutoReload(TIM2, 10); + LL_TIM_SetCounter(TIM2, 0); + LL_TIM_EnableUpdateEvent(TIM2); + LL_TIM_EnableDMAReq_UPDATE(TIM2); + + // Start transactions + LL_TIM_GenerateEvent_UPDATE(TIM2); // Do we really need it? + LL_TIM_EnableCounter(TIM2); + + while(!LL_DMA_IsActiveFlag_TC2(DMA1)) + ; + + LL_DMA_ClearFlag_TC1(DMA1); + LL_DMA_ClearFlag_TC2(DMA1); + LL_TIM_DisableCounter(TIM2); + LL_TIM_SetCounter(TIM2, 0); + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); + + return true; +} diff --git a/lib/digital_signal/digital_signal.h b/lib/digital_signal/digital_signal.h new file mode 100644 index 00000000..5e20e733 --- /dev/null +++ b/lib/digital_signal/digital_signal.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +#include + +typedef struct { + bool start_level; + uint32_t edge_cnt; + uint32_t edges_max_cnt; + float* edge_timings; + uint32_t* reload_reg_buff; +} DigitalSignal; + +DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt); + +void digital_signal_free(DigitalSignal* signal); + +bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b); + +bool digital_signal_get_start_level(DigitalSignal* signal); + +uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal); + +float digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num); + +bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio); diff --git a/lib/flipper_format/flipper_format.c b/lib/flipper_format/flipper_format.c index cee76701..df848ead 100644 --- a/lib/flipper_format/flipper_format.c +++ b/lib/flipper_format/flipper_format.c @@ -185,6 +185,37 @@ bool flipper_format_write_string_cstr( return result; } +bool flipper_format_read_hex_uint64( + FlipperFormat* flipper_format, + const char* key, + uint64_t* data, + const uint16_t data_size) { + furi_assert(flipper_format); + return flipper_format_stream_read_value_line( + flipper_format->stream, + key, + FlipperStreamValueHexUint64, + data, + data_size, + flipper_format->strict_mode); +} + +bool flipper_format_write_hex_uint64( + FlipperFormat* flipper_format, + const char* key, + const uint64_t* data, + const uint16_t data_size) { + furi_assert(flipper_format); + FlipperStreamWriteData write_data = { + .key = key, + .type = FlipperStreamValueHexUint64, + .data = data, + .data_size = data_size, + }; + bool result = flipper_format_stream_write_value_line(flipper_format->stream, &write_data); + return result; +} + bool flipper_format_read_uint32( FlipperFormat* flipper_format, const char* key, diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index 49e3f936..e32a5219 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -273,6 +273,34 @@ bool flipper_format_write_string_cstr( const char* key, const char* data); +/** + * Read array of uint64 in hex format by key + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_format_read_hex_uint64( + FlipperFormat* flipper_format, + const char* key, + uint64_t* data, + const uint16_t data_size); + +/** + * Write key and array of uint64 in hex format + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_format_write_hex_uint64( + FlipperFormat* flipper_format, + const char* key, + const uint64_t* data, + const uint16_t data_size); + /** * Read array of uint32 by key * @param flipper_format Pointer to a FlipperFormat instance diff --git a/lib/flipper_format/flipper_format_stream.c b/lib/flipper_format/flipper_format_stream.c index bd700849..5c210536 100644 --- a/lib/flipper_format/flipper_format_stream.c +++ b/lib/flipper_format/flipper_format_stream.c @@ -287,6 +287,11 @@ bool flipper_format_stream_write_value_line(Stream* stream, FlipperStreamWriteDa const uint32_t* data = write_data->data; string_printf(value, "%" PRId32, data[i]); }; break; + case FlipperStreamValueHexUint64: { + const uint64_t* data = write_data->data; + string_printf( + value, "%08lX%08lX", (uint32_t)(data[i] >> 32), (uint32_t)data[i]); + }; break; case FlipperStreamValueBool: { const bool* data = write_data->data; string_printf(value, data[i] ? "true" : "false"); @@ -380,6 +385,14 @@ bool flipper_format_stream_read_value_line( uint32_t* data = _data; scan_values = sscanf(string_get_cstr(value), "%" PRId32, &data[i]); }; break; + case FlipperStreamValueHexUint64: { + uint64_t* data = _data; + if(string_size(value) >= 16) { + if(hex_chars_to_uint64(string_get_cstr(value), &data[i])) { + scan_values = 1; + } + } + }; break; case FlipperStreamValueBool: { bool* data = _data; data[i] = !string_cmpi_str(value, "true"); diff --git a/lib/flipper_format/flipper_format_stream.h b/lib/flipper_format/flipper_format_stream.h index 6ac17322..75eaef20 100644 --- a/lib/flipper_format/flipper_format_stream.h +++ b/lib/flipper_format/flipper_format_stream.h @@ -15,6 +15,7 @@ typedef enum { FlipperStreamValueFloat, FlipperStreamValueInt32, FlipperStreamValueUint32, + FlipperStreamValueHexUint64, FlipperStreamValueBool, } FlipperStreamValue; diff --git a/lib/lib.mk b/lib/lib.mk index cb58fade..43f4a1a2 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -95,6 +95,11 @@ C_SOURCES += $(wildcard $(LIB_DIR)/toolbox/*/*.c) CPP_SOURCES += $(wildcard $(LIB_DIR)/toolbox/*.cpp) CPP_SOURCES += $(wildcard $(LIB_DIR)/toolbox/*/*.cpp) +# Digital signal +CFLAGS += -I$(LIB_DIR)/digital_signal +C_SOURCES += $(wildcard $(LIB_DIR)/digital_signal/*.c) + + # USB Stack CFLAGS += -I$(LIB_DIR)/libusb_stm32/inc C_SOURCES += $(LIB_DIR)/libusb_stm32/src/usbd_stm32wb55_devfs.c diff --git a/lib/nfc_protocols/crypto1.c b/lib/nfc_protocols/crypto1.c index 469b0de0..f08164ba 100644 --- a/lib/nfc_protocols/crypto1.c +++ b/lib/nfc_protocols/crypto1.c @@ -58,7 +58,7 @@ uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) { return out; } -uint8_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { furi_assert(crypto1); uint32_t out = 0; for(uint8_t i = 0; i < 32; i++) { diff --git a/lib/nfc_protocols/crypto1.h b/lib/nfc_protocols/crypto1.h index aaa2470c..07b39c22 100644 --- a/lib/nfc_protocols/crypto1.h +++ b/lib/nfc_protocols/crypto1.h @@ -16,7 +16,7 @@ uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted); uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted); -uint8_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted); +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted); uint32_t crypto1_filter(uint32_t in); diff --git a/lib/nfc_protocols/mifare_classic.c b/lib/nfc_protocols/mifare_classic.c index ace37bff..38c47127 100644 --- a/lib/nfc_protocols/mifare_classic.c +++ b/lib/nfc_protocols/mifare_classic.c @@ -1,6 +1,7 @@ #include "mifare_classic.h" #include "nfca.h" #include "nfc_util.h" +#include // Algorithm from https://github.com/RfidResearchGroup/proxmark3.git @@ -10,6 +11,20 @@ #define MF_CLASSIC_AUTH_KEY_B_CMD (0x61U) #define MF_CLASSIC_READ_SECT_CMD (0x30) +typedef enum { + MfClassicActionDataRead, + MfClassicActionDataWrite, + MfClassicActionDataInc, + MfClassicActionDataDec, + + MfClassicActionKeyARead, + MfClassicActionKeyAWrite, + MfClassicActionKeyBRead, + MfClassicActionKeyBWrite, + MfClassicActionACRead, + MfClassicActionACWrite, +} MfClassicAction; + static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) { furi_assert(sector < 40); if(sector < 32) { @@ -19,11 +34,31 @@ static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) { } } +static uint8_t mf_classic_get_sector_by_block(uint8_t block) { + if(block < 128) { + return (block | 0x03) / 4; + } else { + return 32 + ((block | 0xf) - 32 * 4) / 16; + } +} + static uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) { furi_assert(sector < 40); return sector < 32 ? 4 : 16; } +static uint8_t mf_classic_get_sector_trailer(uint8_t block) { + if(block < 128) { + return block | 0x03; + } else { + return block | 0x0f; + } +} + +static bool mf_classic_is_sector_trailer(uint8_t block) { + return block == mf_classic_get_sector_trailer(block); +} + uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader) { furi_assert(reader); if(reader->type == MfClassicType1k) { @@ -35,6 +70,132 @@ uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader) { } } +static uint16_t mf_classic_get_total_block_num(MfClassicType type) { + if(type == MfClassicType1k) { + return 64; + } else if(type == MfClassicType4k) { + return 256; + } else { + return 0; + } +} + +static bool mf_classic_is_allowed_access_sector_trailer( + MfClassicEmulator* emulator, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action) { + uint8_t* sector_trailer = emulator->data.block[block_num].value; + uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) | + ((sector_trailer[8] >> 7) & 0x01); + switch(action) { + case MfClassicActionKeyARead: { + return false; + } + case MfClassicActionKeyAWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03))); + } + case MfClassicActionKeyBRead: { + return (key == MfClassicKeyA && (AC == 0x00 || AC == 0x02 || AC == 0x01)); + } + case MfClassicActionKeyBWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03))); + } + case MfClassicActionACRead: { + return ( + (key == MfClassicKeyA) || + (key == MfClassicKeyB && !(AC == 0x00 || AC == 0x02 || AC == 0x01))); + } + case MfClassicActionACWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x03 || AC == 0x05))); + } + default: + return false; + } + return true; +} + +static bool mf_classic_is_allowed_access_data_block( + MfClassicEmulator* emulator, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action) { + uint8_t* sector_trailer = emulator->data.block[mf_classic_get_sector_trailer(block_num)].value; + + uint8_t sector_block; + if(block_num <= 128) { + sector_block = block_num & 0x03; + } else { + sector_block = (block_num & 0x0f) / 5; + } + + uint8_t AC; + switch(sector_block) { + case 0x00: { + AC = ((sector_trailer[7] >> 2) & 0x04) | ((sector_trailer[8] << 1) & 0x02) | + ((sector_trailer[8] >> 4) & 0x01); + break; + } + case 0x01: { + AC = ((sector_trailer[7] >> 3) & 0x04) | ((sector_trailer[8] >> 0) & 0x02) | + ((sector_trailer[8] >> 5) & 0x01); + break; + } + case 0x02: { + AC = ((sector_trailer[7] >> 4) & 0x04) | ((sector_trailer[8] >> 1) & 0x02) | + ((sector_trailer[8] >> 6) & 0x01); + break; + } + default: + return false; + } + + switch(action) { + case MfClassicActionDataRead: { + return ( + (key == MfClassicKeyA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) || + (key == MfClassicKeyB && !(AC == 0x07))); + } + case MfClassicActionDataWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x00)) || + (key == MfClassicKeyB && (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03))); + } + case MfClassicActionDataInc: { + return ( + (key == MfClassicKeyA && (AC == 0x00)) || + (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06))); + } + case MfClassicActionDataDec: { + return ( + (key == MfClassicKeyA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06 || AC == 0x01))); + } + default: + return false; + } + + return false; +} + +static bool mf_classic_is_allowed_access( + MfClassicEmulator* emulator, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action) { + if(mf_classic_is_sector_trailer(block_num)) { + return mf_classic_is_allowed_access_sector_trailer(emulator, block_num, key, action); + } else { + return mf_classic_is_allowed_access_data_block(emulator, block_num, key, action); + } +} + bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { UNUSED(ATQA1); if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) { @@ -120,7 +281,7 @@ static bool mf_classic_auth( tx_rx->tx_data[1] = block; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxNoCrc; tx_rx->tx_bits = 2 * 8; - if(!furi_hal_nfc_tx_rx(tx_rx, 5)) break; + if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break; uint32_t nt = (uint32_t)nfc_util_bytes2num(tx_rx->rx_data, 4); crypto1_init(crypto, key); @@ -142,7 +303,7 @@ static bool mf_classic_auth( } tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; tx_rx->tx_bits = 8 * 8; - if(!furi_hal_nfc_tx_rx(tx_rx, 5)) break; + if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break; if(tx_rx->rx_bits == 32) { crypto1_word(crypto, 0, 0); auth_success = true; @@ -296,6 +457,8 @@ uint8_t mf_classic_read_card( uint8_t sectors_read = 0; data->type = reader->type; + data->key_a_mask = 0; + data->key_b_mask = 0; MfClassicSector temp_sector = {}; for(uint8_t i = 0; i < reader->sectors_to_read; i++) { if(mf_classic_read_sector( @@ -305,9 +468,279 @@ uint8_t mf_classic_read_card( for(uint8_t j = 0; j < temp_sector.total_blocks; j++) { data->block[first_block + j] = temp_sector.block[j]; } + if(reader->sector_reader[i].key_a != MF_CLASSIC_NO_KEY) { + data->key_a_mask |= 1 << reader->sector_reader[i].sector_num; + } + if(reader->sector_reader[i].key_b != MF_CLASSIC_NO_KEY) { + data->key_b_mask |= 1 << reader->sector_reader[i].sector_num; + } sectors_read++; } } return sectors_read; } + +void mf_crypto1_decrypt( + Crypto1* crypto, + uint8_t* encrypted_data, + uint16_t encrypted_data_bits, + uint8_t* decrypted_data) { + if(encrypted_data_bits < 8) { + uint8_t decrypted_byte = 0; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3; + decrypted_data[0] = decrypted_byte; + } else { + for(size_t i = 0; i < encrypted_data_bits / 8; i++) { + decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i]; + } + } +} + +void mf_crypto1_encrypt( + Crypto1* crypto, + uint8_t* keystream, + uint8_t* plain_data, + uint16_t plain_data_bits, + uint8_t* encrypted_data, + uint8_t* encrypted_parity) { + if(plain_data_bits < 8) { + encrypted_data[0] = 0; + for(size_t i = 0; i < plain_data_bits; i++) { + encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i; + } + } else { + memset(encrypted_parity, 0, plain_data_bits / 8 + 1); + for(uint8_t i = 0; i < plain_data_bits / 8; i++) { + encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^ + plain_data[i]; + encrypted_parity[i / 8] |= + (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01) + << (7 - (i & 0x0007))); + } + } +} + +bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(emulator); + furi_assert(tx_rx); + bool command_processed = false; + bool is_encrypted = false; + uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE]; + MfClassicKey access_key = MfClassicKeyA; + + // Read command + while(!command_processed) { + if(!is_encrypted) { + // Read first frame + tx_rx->tx_bits = 0; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + } + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + FURI_LOG_D( + TAG, "Error in tx rx. Tx :%d bits, Rx: %d bits", tx_rx->tx_bits, tx_rx->rx_bits); + break; + } + if(!is_encrypted) { + memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8); + } else { + mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); + } + // TODO Check crc + + if(plain_data[0] == 0x50 && plain_data[1] == 00) { + FURI_LOG_T(TAG, "Halt received"); + command_processed = true; + break; + } else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) { + uint8_t block = plain_data[1]; + uint64_t key = 0; + uint8_t sector_trailer_block = mf_classic_get_sector_trailer(block); + MfClassicSectorTrailer* sector_trailer = + (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; + if(plain_data[0] == 0x61) { + key = nfc_util_bytes2num(sector_trailer->key_b, 6); + access_key = MfClassicKeyA; + } else { + key = nfc_util_bytes2num(sector_trailer->key_a, 6); + access_key = MfClassicKeyB; + } + + uint32_t nonce = prng_successor(DWT->CYCCNT, 32); + uint8_t nt[4]; + uint8_t nt_keystream[4]; + nfc_util_num2bytes(nonce, 4, nt); + nfc_util_num2bytes(nonce ^ emulator->cuid, 4, nt_keystream); + crypto1_init(&emulator->crypto, key); + if(!is_encrypted) { + crypto1_word(&emulator->crypto, emulator->cuid ^ nonce, 0); + memcpy(tx_rx->tx_data, nt, sizeof(nt)); + tx_rx->tx_bits = sizeof(nt) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxRaw; + } else { + mf_crypto1_encrypt( + &emulator->crypto, + nt_keystream, + nt, + sizeof(nt) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = sizeof(nt) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + } + if(!furi_hal_nfc_tx_rx(tx_rx, 500)) { + FURI_LOG_E(TAG, "Error in NT exchange"); + command_processed = true; + break; + } + + if(tx_rx->rx_bits != 64) { + FURI_LOG_W(TAG, "Incorrect nr + ar"); + command_processed = true; + break; + } + + // Check if we store valid key + if(access_key == MfClassicKeyA) { + if(FURI_BIT(emulator->data.key_a_mask, mf_classic_get_sector_by_block(block)) == + 0) { + FURI_LOG_D(TAG, "Unsupported sector key A for block %d", sector_trailer_block); + break; + } + } else if(access_key == MfClassicKeyB) { + if(FURI_BIT(emulator->data.key_b_mask, mf_classic_get_sector_by_block(block)) == + 0) { + FURI_LOG_D(TAG, "Unsupported sector key B for block %d", sector_trailer_block); + break; + } + } + + uint32_t nr = nfc_util_bytes2num(tx_rx->rx_data, 4); + uint32_t ar = nfc_util_bytes2num(&tx_rx->rx_data[4], 4); + crypto1_word(&emulator->crypto, nr, 1); + uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0); + if(cardRr != prng_successor(nonce, 64)) { + FURI_LOG_T(TAG, "Wrong AUTH! %08X != %08X", cardRr, prng_successor(nonce, 64)); + // Don't send NACK, as tag don't send it + command_processed = true; + break; + } + + uint32_t ans = prng_successor(nonce, 96); + uint8_t responce[4] = {}; + nfc_util_num2bytes(ans, 4, responce); + mf_crypto1_encrypt( + &emulator->crypto, + NULL, + responce, + sizeof(responce) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = sizeof(responce) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + + is_encrypted = true; + } else if(is_encrypted && plain_data[0] == 0x30) { + uint8_t block = plain_data[1]; + uint8_t block_data[18] = {}; + memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); + if(mf_classic_is_sector_trailer(block)) { + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyARead)) { + memset(block_data, 0, 6); + } + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyBRead)) { + memset(&block_data[10], 0, 6); + } + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionACRead)) { + memset(&block_data[6], 0, 4); + } + } else { + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionDataRead)) { + memset(block_data, 0, 16); + } + } + nfca_append_crc16(block_data, 16); + + mf_crypto1_encrypt( + &emulator->crypto, + NULL, + block_data, + sizeof(block_data) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = 18 * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + } else if(is_encrypted && plain_data[0] == 0xA0) { + uint8_t block = plain_data[1]; + if(block > mf_classic_get_total_block_num(emulator->data.type)) { + break; + } + // Send ACK + uint8_t ack = 0x0A; + mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; + if(tx_rx->rx_bits != 18 * 8) break; + + mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); + uint8_t block_data[16] = {}; + memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); + if(mf_classic_is_sector_trailer(block)) { + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyAWrite)) { + memcpy(block_data, plain_data, 6); + } + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyBWrite)) { + memcpy(&block_data[10], &plain_data[10], 6); + } + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionACWrite)) { + memcpy(&block_data[6], &plain_data[6], 4); + } + } else { + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionDataWrite)) { + memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE); + } + } + if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE)) { + memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE); + emulator->data_changed = true; + } + // Send ACK + ack = 0x0A; + mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + } else { + // Unknown command + break; + } + } + + if(!command_processed) { + // Send NACK + uint8_t nack = 0x04; + if(is_encrypted) { + mf_crypto1_encrypt( + &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); + } else { + tx_rx->tx_data[0] = nack; + } + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + furi_hal_nfc_tx_rx(tx_rx, 300); + } + + return true; +} diff --git a/lib/nfc_protocols/mifare_classic.h b/lib/nfc_protocols/mifare_classic.h index fa778b77..bbf34b2d 100644 --- a/lib/nfc_protocols/mifare_classic.h +++ b/lib/nfc_protocols/mifare_classic.h @@ -13,6 +13,7 @@ #define MF_CLASSIC_BLOCKS_IN_SECTOR_MAX (16) #define MF_CLASSIC_NO_KEY (0xFFFFFFFFFFFFFFFF) +#define MF_CLASSIC_MAX_DATA_SIZE (16) typedef enum { MfClassicType1k, @@ -41,6 +42,8 @@ typedef struct { typedef struct { MfClassicType type; + uint64_t key_a_mask; + uint64_t key_b_mask; MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX]; } MfClassicData; @@ -65,6 +68,13 @@ typedef struct { MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX]; } MfClassicReader; +typedef struct { + uint32_t cuid; + Crypto1 crypto; + MfClassicData data; + bool data_changed; +} MfClassicEmulator; + bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); bool mf_classic_get_type( @@ -100,3 +110,5 @@ uint8_t mf_classic_read_card( FuriHalNfcTxRxContext* tx_rx, MfClassicReader* reader, MfClassicData* data); + +bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx); diff --git a/lib/nfc_protocols/nfca.c b/lib/nfc_protocols/nfca.c index 81a6ddfc..e1cbdafe 100755 --- a/lib/nfc_protocols/nfca.c +++ b/lib/nfc_protocols/nfca.c @@ -1,11 +1,17 @@ #include "nfca.h" #include #include +#include #define NFCA_CMD_RATS (0xE0U) #define NFCA_CRC_INIT (0x6363) +#define NFCA_F_SIG (13560000.0) +#define NFCA_T_SIG (1.0 / NFCA_F_SIG) + +#define NFCA_SIGNAL_MAX_EDGES (1350) + typedef struct { uint8_t cmd; uint8_t param; @@ -53,3 +59,81 @@ bool nfca_emulation_handler( return sleep; } + +static void nfca_add_bit(DigitalSignal* signal, bool bit) { + if(bit) { + signal->start_level = true; + for(size_t i = 0; i < 7; i++) { + signal->edge_timings[i] = 8 * NFCA_T_SIG; + } + signal->edge_timings[7] = 9 * 8 * NFCA_T_SIG; + signal->edge_cnt = 8; + } else { + signal->start_level = false; + signal->edge_timings[0] = 8 * 8 * NFCA_T_SIG; + for(size_t i = 1; i < 9; i++) { + signal->edge_timings[i] = 8 * NFCA_T_SIG; + } + signal->edge_cnt = 9; + } +} + +static void nfca_add_byte(NfcaSignal* nfca_signal, uint8_t byte, bool parity) { + for(uint8_t i = 0; i < 8; i++) { + if(byte & (1 << i)) { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + } else { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); + } + } + if(parity) { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + } else { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); + } +} + +NfcaSignal* nfca_signal_alloc() { + NfcaSignal* nfca_signal = malloc(sizeof(NfcaSignal)); + nfca_signal->one = digital_signal_alloc(10); + nfca_signal->zero = digital_signal_alloc(10); + nfca_add_bit(nfca_signal->one, true); + nfca_add_bit(nfca_signal->zero, false); + nfca_signal->tx_signal = digital_signal_alloc(NFCA_SIGNAL_MAX_EDGES); + + return nfca_signal; +} + +void nfca_signal_free(NfcaSignal* nfca_signal) { + furi_assert(nfca_signal); + + digital_signal_free(nfca_signal->one); + digital_signal_free(nfca_signal->zero); + digital_signal_free(nfca_signal->tx_signal); + free(nfca_signal); +} + +void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity) { + furi_assert(nfca_signal); + furi_assert(data); + furi_assert(parity); + + nfca_signal->tx_signal->edge_cnt = 0; + nfca_signal->tx_signal->start_level = true; + // Start of frame + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + + if(bits < 8) { + for(size_t i = 0; i < bits; i++) { + if(FURI_BIT(data[0], i)) { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + } else { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); + } + } + } else { + for(size_t i = 0; i < bits / 8; i++) { + nfca_add_byte(nfca_signal, data[i], parity[i / 8] & (1 << (7 - (i & 0x07)))); + } + } +} diff --git a/lib/nfc_protocols/nfca.h b/lib/nfc_protocols/nfca.h index 73e2e65e..498ef284 100644 --- a/lib/nfc_protocols/nfca.h +++ b/lib/nfc_protocols/nfca.h @@ -3,6 +3,14 @@ #include #include +#include + +typedef struct { + DigitalSignal* one; + DigitalSignal* zero; + DigitalSignal* tx_signal; +} NfcaSignal; + uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len); void nfca_append_crc16(uint8_t* buff, uint16_t len); @@ -12,3 +20,9 @@ bool nfca_emulation_handler( uint16_t buff_rx_len, uint8_t* buff_tx, uint16_t* buff_tx_len); + +NfcaSignal* nfca_signal_alloc(); + +void nfca_signal_free(NfcaSignal* nfca_signal); + +void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity); diff --git a/lib/toolbox/hex.c b/lib/toolbox/hex.c index 739eb30b..41bb24bb 100644 --- a/lib/toolbox/hex.c +++ b/lib/toolbox/hex.c @@ -26,3 +26,14 @@ bool hex_chars_to_uint8(char hi, char low, uint8_t* value) { return false; } } + +bool hex_chars_to_uint64(const char* value_str, uint64_t* value) { + uint8_t* _value = (uint8_t*)value; + bool parse_success = false; + + for(uint8_t i = 0; i < 8; i++) { + parse_success = hex_chars_to_uint8(value_str[i * 2], value_str[i * 2 + 1], &_value[7 - i]); + if(!parse_success) break; + } + return parse_success; +} diff --git a/lib/toolbox/hex.h b/lib/toolbox/hex.h index ca10f2be..c6683a52 100644 --- a/lib/toolbox/hex.h +++ b/lib/toolbox/hex.h @@ -6,23 +6,31 @@ extern "C" { #endif -/** - * Convert ASCII hex value to nibble - * @param c ASCII character - * @param nibble nibble pointer, output - * @return bool conversion status +/** Convert ASCII hex value to nibble + * @param c ASCII character + * @param nibble nibble pointer, output + * + * @return bool conversion status */ bool hex_char_to_hex_nibble(char c, uint8_t* nibble); -/** - * Convert ASCII hex values to byte - * @param hi hi nibble text - * @param low low nibble text - * @param value output value - * @return bool conversion status +/** Convert ASCII hex values to byte + * @param hi hi nibble text + * @param low low nibble text + * @param value output value + * + * @return bool conversion status */ bool hex_chars_to_uint8(char hi, char low, uint8_t* value); +/** Convert ASCII hex values to uint64_t + * @param value_str ASCII 64 bi data + * @param value output value + * + * @return bool conversion status + */ +bool hex_chars_to_uint64(const char* value_str, uint64_t* value); + #ifdef __cplusplus } #endif From 4b45746b8e10420887f40caf0faae41632a8a70e Mon Sep 17 00:00:00 2001 From: Kevin Wallace <184975+kevinwallace@users.noreply.github.com> Date: Tue, 24 May 2022 07:05:46 -0700 Subject: [PATCH 17/28] Fix MiFare DESFire GetKeySettings response parsing (#1267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only the bottom nybble of buf[1] is the key ID; the top nybble contains flags. Notably, the top bit is set for AES mode on EV1+ cards, and ORCA cards in the Seattle area were just upgraded to include an app that uses AES. Prior to this, cards with flags set could be read and saved just fine, but loading them would fail due to missing keys. After this, tags saved with the old version will load fine, and when re-saved will have the flags separated out into a new field. Co-authored-by: Kevin Wallace Co-authored-by: gornekich Co-authored-by: あく --- applications/nfc/nfc_device.c | 10 ++++++++++ lib/nfc_protocols/mifare_desfire.c | 6 +++++- lib/nfc_protocols/mifare_desfire.h | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/applications/nfc/nfc_device.c b/applications/nfc/nfc_device.c index 0771a1ef..63f0c3cc 100644 --- a/applications/nfc/nfc_device.c +++ b/applications/nfc/nfc_device.c @@ -195,6 +195,10 @@ static bool nfc_device_save_mifare_df_key_settings( string_printf(key, "%s Key Changeable", prefix); if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) break; + if(ks->flags) { + string_printf(key, "%s Flags", prefix); + if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->flags, 1)) break; + } string_printf(key, "%s Max Keys", prefix); if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break; for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { @@ -230,8 +234,14 @@ bool nfc_device_load_mifare_df_key_settings( string_printf(key, "%s Key Changeable", prefix); if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) break; + string_printf(key, "%s Flags", prefix); + if(flipper_format_key_exist(file, string_get_cstr(key))) { + if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->flags, 1)) break; + } string_printf(key, "%s Max Keys", prefix); if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break; + ks->flags |= ks->max_keys >> 4; + ks->max_keys &= 0xF; MifareDesfireKeyVersion** kv_head = &ks->key_version_head; for(int key_id = 0; key_id < ks->max_keys; key_id++) { string_printf(key, "%s Key %d Version", prefix, key_id); diff --git a/lib/nfc_protocols/mifare_desfire.c b/lib/nfc_protocols/mifare_desfire.c index 4f02e839..6f28dc5d 100644 --- a/lib/nfc_protocols/mifare_desfire.c +++ b/lib/nfc_protocols/mifare_desfire.c @@ -115,6 +115,9 @@ void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, string_t out) { string_cat_printf(out, "freeCreateDelete %d\n", ks->free_create_delete); string_cat_printf(out, "freeDirectoryList %d\n", ks->free_directory_list); string_cat_printf(out, "masterChangeable %d\n", ks->master_key_changeable); + if(ks->flags) { + string_cat_printf(out, "flags %d\n", ks->flags); + } string_cat_printf(out, "maxKeys %d\n", ks->max_keys); for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { string_cat_printf(out, "key %d version %d\n", kv->id, kv->version); @@ -274,7 +277,8 @@ bool mf_df_parse_get_key_settings_response( out->free_create_delete = (buf[0] & 0x4) != 0; out->free_directory_list = (buf[0] & 0x2) != 0; out->master_key_changeable = (buf[0] & 0x1) != 0; - out->max_keys = buf[1]; + out->flags = buf[1] >> 4; + out->max_keys = buf[1] & 0xF; return true; } diff --git a/lib/nfc_protocols/mifare_desfire.h b/lib/nfc_protocols/mifare_desfire.h index 809f1782..dbe0802e 100644 --- a/lib/nfc_protocols/mifare_desfire.h +++ b/lib/nfc_protocols/mifare_desfire.h @@ -56,6 +56,7 @@ typedef struct { bool free_create_delete; bool free_directory_list; bool master_key_changeable; + uint8_t flags; uint8_t max_keys; MifareDesfireKeyVersion* key_version_head; } MifareDesfireKeySettings; From 0d5d4c8688a96c77ff84df2b498769bfb8c1da0d Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Thu, 26 May 2022 06:55:29 -0600 Subject: [PATCH 18/28] RFC: NTAG I2C support (#1227) * nfc: Add NTAG I2C (Plus) 1K/2K read support * nfc: Add rudimentary NTAG I2C emulation * nfc: Closer NTAG I2C emulation plus debug logging * nfc: Fix NTAG I2C sector select emulation * nfc: Add security for NTAG I2C * nfc: Send NAK correctly for MFUL reads * nfc: Better emulate NTAG I2C SECTOR_SELECT behavior * nfc: Fix non-I2C Ultralight read Per datasheet, max sector for SECTOR_SELECT is 0xfe, so 0xff is OK as uninit value * nfc: Only read sig for NTAG if supported Attempting to read signature breaks immediate call to sector select on NTAG I2C original for some reason, so don't read signature if the command is not supported Co-authored-by: gornekich --- applications/nfc/nfc_types.c | 8 + applications/nfc/nfc_worker.c | 5 + firmware/targets/f7/furi_hal/furi_hal_nfc.c | 3 + .../targets/furi_hal_include/furi_hal_nfc.h | 4 + lib/nfc_protocols/mifare_ultralight.c | 877 ++++++++++++++++-- lib/nfc_protocols/mifare_ultralight.h | 18 +- 6 files changed, 813 insertions(+), 102 deletions(-) mode change 100755 => 100644 firmware/targets/f7/furi_hal/furi_hal_nfc.c diff --git a/applications/nfc/nfc_types.c b/applications/nfc/nfc_types.c index 1b67284c..2d11c339 100644 --- a/applications/nfc/nfc_types.c +++ b/applications/nfc/nfc_types.c @@ -35,6 +35,14 @@ const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) { return "NTAG215"; } else if(type == MfUltralightTypeNTAG216) { return "NTAG216"; + } else if(type == MfUltralightTypeNTAGI2C1K) { + return "NTAG I2C 1K"; + } else if(type == MfUltralightTypeNTAGI2C2K) { + return "NTAG I2C 2K"; + } else if(type == MfUltralightTypeNTAGI2CPlus1K) { + return "NTAG I2C Plus 1K"; + } else if(type == MfUltralightTypeNTAGI2CPlus2K) { + return "NTAG I2C Plus 2K"; } else if(type == MfUltralightTypeUL11 && full_name) { return "Mifare Ultralight 11"; } else if(type == MfUltralightTypeUL21 && full_name) { diff --git a/applications/nfc/nfc_worker.c b/applications/nfc/nfc_worker.c index 5ae99d6d..88729bf4 100644 --- a/applications/nfc/nfc_worker.c +++ b/applications/nfc/nfc_worker.c @@ -315,6 +315,11 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) { MfUltralightEmulator emulator = {}; mf_ul_prepare_emulation(&emulator, &nfc_worker->dev_data->mf_ul_data); while(nfc_worker->state == NfcWorkerStateEmulateMifareUltralight) { + emulator.auth_success = false; + if(emulator.data.type >= MfUltralightTypeNTAGI2C1K) { + // Sector index needs to be reset + emulator.curr_sector = 0; + } furi_hal_nfc_emulate_nfca( nfc_data->uid, nfc_data->uid_len, diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c old mode 100755 new mode 100644 index 4391685e..d1bdb5d7 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -1,3 +1,4 @@ +#include #include "furi_hal_nfc.h" #include #include @@ -337,6 +338,8 @@ bool furi_hal_nfc_emulate_nfca( break; } if(buff_tx_len) { + if(buff_tx_len == UINT16_MAX) buff_tx_len = 0; + ReturnCode ret = rfalTransceiveBitsBlockingTx( buff_tx, buff_tx_len, diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h index 860db80d..30672fb9 100755 --- a/firmware/targets/furi_hal_include/furi_hal_nfc.h +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -36,6 +36,10 @@ extern "C" { ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | \ (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE) +#define FURI_HAL_NFC_TX_RAW_RX_DEFAULT \ + ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_REMV | \ + (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE) + typedef enum { FuriHalNfcTxRxTypeDefault, FuriHalNfcTxRxTypeRxNoCrc, diff --git a/lib/nfc_protocols/mifare_ultralight.c b/lib/nfc_protocols/mifare_ultralight.c index 5b3d70b5..fb425ea7 100644 --- a/lib/nfc_protocols/mifare_ultralight.c +++ b/lib/nfc_protocols/mifare_ultralight.c @@ -1,5 +1,7 @@ +#include #include "mifare_ultralight.h" #include +#include #define TAG "MfUltralight" @@ -16,6 +18,7 @@ static void mf_ul_set_default_version(MfUltralightReader* reader, MfUltralightDa reader->support_fast_read = false; reader->support_tearing_flags = false; reader->support_counters = false; + reader->support_signature = false; } bool mf_ultralight_read_version( @@ -44,30 +47,71 @@ bool mf_ultralight_read_version( reader->support_fast_read = true; reader->support_tearing_flags = true; reader->support_counters = true; + reader->support_signature = true; } else if(version->storage_size == 0x0E) { data->type = MfUltralightTypeUL21; reader->pages_to_read = 41; reader->support_fast_read = true; reader->support_tearing_flags = true; reader->support_counters = true; + reader->support_signature = true; } else if(version->storage_size == 0x0F) { data->type = MfUltralightTypeNTAG213; reader->pages_to_read = 45; reader->support_fast_read = true; reader->support_tearing_flags = false; reader->support_counters = false; + reader->support_signature = true; } else if(version->storage_size == 0x11) { data->type = MfUltralightTypeNTAG215; reader->pages_to_read = 135; reader->support_fast_read = true; reader->support_tearing_flags = false; reader->support_counters = false; + reader->support_signature = true; + } else if(version->prod_subtype == 5 && version->prod_ver_major == 2) { + // NTAG I2C + bool known = false; + if(version->prod_ver_minor == 1) { + if(version->storage_size == 0x13) { + data->type = MfUltralightTypeNTAGI2C1K; + reader->pages_to_read = 231; + reader->support_signature = false; + known = true; + } else if(version->storage_size == 0x15) { + data->type = MfUltralightTypeNTAGI2C2K; + reader->pages_to_read = 485; + reader->support_signature = false; + known = true; + } + } else if(version->prod_ver_minor == 2) { + if(version->storage_size == 0x13) { + data->type = MfUltralightTypeNTAGI2CPlus1K; + reader->pages_to_read = 236; + reader->support_signature = true; + known = true; + } else if(version->storage_size == 0x15) { + data->type = MfUltralightTypeNTAGI2CPlus2K; + reader->pages_to_read = 492; + reader->support_signature = true; + known = true; + } + } + + if(known) { + reader->support_fast_read = true; + reader->support_tearing_flags = false; + reader->support_counters = false; + } else { + mf_ul_set_default_version(reader, data); + } } else if(version->storage_size == 0x13) { data->type = MfUltralightTypeNTAG216; reader->pages_to_read = 231; reader->support_fast_read = true; reader->support_tearing_flags = false; reader->support_counters = false; + reader->support_signature = true; } else { mf_ul_set_default_version(reader, data); break; @@ -78,26 +122,351 @@ bool mf_ultralight_read_version( return version_read; } +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_1k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 226: sector 0 + // 227 - 228: config registers + // 229 - 230: session registers + + if(linear_address > 230) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 229) { + *sector = 3; + *valid_pages = 2 - (linear_address - 229); + return linear_address - 229 + 248; + } else if(linear_address >= 227) { + *sector = 0; + *valid_pages = 2 - (linear_address - 227); + return linear_address - 227 + 232; + } else { + *sector = 0; + *valid_pages = 227 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_2k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 255: sector 0 + // 256 - 480: sector 1 + // 481 - 482: config registers + // 483 - 484: session registers + + if(linear_address > 484) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 483) { + *sector = 3; + *valid_pages = 2 - (linear_address - 483); + return linear_address - 483 + 248; + } else if(linear_address >= 481) { + *sector = 1; + *valid_pages = 2 - (linear_address - 481); + return linear_address - 481 + 232; + } else if(linear_address >= 256) { + *sector = 1; + *valid_pages = 225 - (linear_address - 256); + return linear_address - 256; + } else { + *sector = 0; + *valid_pages = 256 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_1k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 233: sector 0 + registers + // 234 - 235: session registers + + if(linear_address > 235) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 234) { + *sector = 0; + *valid_pages = 2 - (linear_address - 234); + return linear_address - 234 + 236; + } else { + *sector = 0; + *valid_pages = 234 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_2k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 233: sector 0 + registers + // 234 - 235: session registers + // 236 - 491: sector 1 + + if(linear_address > 491) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 236) { + *sector = 1; + *valid_pages = 256 - (linear_address - 236); + return linear_address - 236; + } else if(linear_address >= 234) { + *sector = 0; + *valid_pages = 2 - (linear_address - 234); + return linear_address - 234 + 236; + } else { + *sector = 0; + *valid_pages = 234 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag( + MfUltralightData* data, + MfUltralightReader* reader, + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + switch(data->type) { + case MfUltralightTypeNTAGI2C1K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_1k(linear_address, sector, valid_pages); + + case MfUltralightTypeNTAGI2C2K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_2k(linear_address, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus1K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_1k(linear_address, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus2K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_2k(linear_address, sector, valid_pages); + + default: + *sector = 0xff; + *valid_pages = reader->pages_to_read - linear_address; + return linear_address; + } +} + +static int16_t + mf_ultralight_ntag_i2c_addr_tag_to_lin_1k(uint8_t page, uint8_t sector, uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + if(page <= 226) { + *valid_pages = 227 - page; + translated_page = page; + valid = true; + } else if(page >= 232 && page <= 233) { + *valid_pages = 2 - (page - 232); + translated_page = page - 232 + 227; + valid = true; + } + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 229; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t + mf_ultralight_ntag_i2c_addr_tag_to_lin_2k(uint8_t page, uint8_t sector, uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + *valid_pages = 256 - page; + translated_page = page; + valid = true; + } else if(sector == 1) { + if(page <= 224) { + *valid_pages = 225 - page; + translated_page = 256 + page; + valid = true; + } else if(page >= 232 && page <= 233) { + *valid_pages = 2 - (page - 232); + translated_page = page - 232 + 481; + valid = true; + } + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 483; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_1k( + uint8_t page, + uint8_t sector, + uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + if(page <= 233) { + *valid_pages = 234 - page; + translated_page = page; + valid = true; + } else if(page >= 236 && page <= 237) { + *valid_pages = 2 - (page - 236); + translated_page = page - 236 + 234; + valid = true; + } + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 234; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_2k( + uint8_t page, + uint8_t sector, + uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + if(page <= 233) { + *valid_pages = 234 - page; + translated_page = page; + valid = true; + } else if(page >= 236 && page <= 237) { + *valid_pages = 2 - (page - 236); + translated_page = page - 236 + 234; + valid = true; + } + } else if(sector == 1) { + *valid_pages = 256 - page; + translated_page = page + 236; + valid = true; + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 234; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin( + MfUltralightData* data, + uint8_t page, + uint8_t sector, + uint16_t* valid_pages) { + switch(data->type) { + case MfUltralightTypeNTAGI2C1K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_1k(page, sector, valid_pages); + + case MfUltralightTypeNTAGI2C2K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_2k(page, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus1K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_1k(page, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus2K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_2k(page, sector, valid_pages); + + default: + *valid_pages = data->data_size / 4 - page; + return page; + } +} + +static bool mf_ultralight_sector_select(FuriHalNfcTxRxContext* tx_rx, uint8_t sector) { + FURI_LOG_D(TAG, "Selecting sector %u", sector); + tx_rx->tx_data[0] = MF_UL_SECTOR_SELECT; + tx_rx->tx_data[1] = 0xff; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { + FURI_LOG_D(TAG, "Failed to issue sector select command"); + return false; + } + + tx_rx->tx_data[0] = sector; + tx_rx->tx_data[1] = 0x00; + tx_rx->tx_data[2] = 0x00; + tx_rx->tx_data[3] = 0x00; + tx_rx->tx_bits = 32; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + // This is NOT a typo! The tag ACKs by not sending a response within 1ms. + if(furi_hal_nfc_tx_rx(tx_rx, 20)) { + // TODO: what gets returned when an actual NAK is received? + FURI_LOG_D(TAG, "Sector %u select NAK'd", sector); + return false; + } + + return true; +} + bool mf_ultralight_read_pages( FuriHalNfcTxRxContext* tx_rx, MfUltralightReader* reader, MfUltralightData* data) { uint8_t pages_read_cnt = 0; + uint8_t curr_sector_index = 0xff; + reader->pages_read = 0; + for(size_t i = 0; i < reader->pages_to_read; i += pages_read_cnt) { + uint8_t tag_sector; + int16_t valid_pages; + int16_t tag_page = mf_ultralight_ntag_i2c_addr_lin_to_tag( + data, reader, (int16_t)i, &tag_sector, &valid_pages); - for(size_t i = 0; i < reader->pages_to_read; i += 4) { - FURI_LOG_D(TAG, "Reading pages %d - %d", i, i + 3); + furi_assert(tag_page != -1); + if(curr_sector_index != tag_sector) { + if(!mf_ultralight_sector_select(tx_rx, tag_sector)) return false; + curr_sector_index = tag_sector; + } + + FURI_LOG_D(TAG, "Reading pages %d - %d", i, i + (valid_pages > 4 ? 4 : valid_pages) - 1); tx_rx->tx_data[0] = MF_UL_READ_CMD; - tx_rx->tx_data[1] = i; + tx_rx->tx_data[1] = tag_page; tx_rx->tx_bits = 16; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { - FURI_LOG_D(TAG, "Failed to read pages %d - %d", i, i + 3); + FURI_LOG_D( + TAG, + "Failed to read pages %d - %d", + i, + i + (valid_pages > 4 ? 4 : valid_pages) - 1); break; } - if(i + 4 <= reader->pages_to_read) { + if(valid_pages > 4) { pages_read_cnt = 4; } else { - pages_read_cnt = reader->pages_to_read - reader->pages_read; + pages_read_cnt = valid_pages; } reader->pages_read += pages_read_cnt; data->data_size = reader->pages_read * 4; @@ -111,18 +480,39 @@ bool mf_ultralight_fast_read_pages( FuriHalNfcTxRxContext* tx_rx, MfUltralightReader* reader, MfUltralightData* data) { - FURI_LOG_D(TAG, "Reading pages 0 - %d", reader->pages_to_read); - tx_rx->tx_data[0] = MF_UL_FAST_READ_CMD; - tx_rx->tx_data[1] = 0; - tx_rx->tx_data[2] = reader->pages_to_read - 1; - tx_rx->tx_bits = 24; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - reader->pages_read = reader->pages_to_read; - data->data_size = reader->pages_read * 4; - memcpy(data->data, tx_rx->rx_data, data->data_size); - } else { - FURI_LOG_D(TAG, "Failed to read pages 0 - %d", reader->pages_to_read); + uint8_t curr_sector_index = 0xff; + reader->pages_read = 0; + while(reader->pages_read < reader->pages_to_read) { + uint8_t tag_sector; + int16_t valid_pages; + int16_t tag_page = mf_ultralight_ntag_i2c_addr_lin_to_tag( + data, reader, reader->pages_read, &tag_sector, &valid_pages); + + furi_assert(tag_page != -1); + if(curr_sector_index != tag_sector) { + if(!mf_ultralight_sector_select(tx_rx, tag_sector)) return false; + curr_sector_index = tag_sector; + } + + FURI_LOG_D( + TAG, "Reading pages %d - %d", reader->pages_read, reader->pages_read + valid_pages - 1); + tx_rx->tx_data[0] = MF_UL_FAST_READ_CMD; + tx_rx->tx_data[1] = tag_page; + tx_rx->tx_data[2] = valid_pages - 1; + tx_rx->tx_bits = 24; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + memcpy(&data->data[reader->pages_read * 4], tx_rx->rx_data, valid_pages * 4); + reader->pages_read += valid_pages; + data->data_size = reader->pages_read * 4; + } else { + FURI_LOG_D( + TAG, + "Failed to read pages %d - %d", + reader->pages_read, + reader->pages_read + valid_pages - 1); + break; + } } return reader->pages_read == reader->pages_to_read; @@ -199,8 +589,10 @@ bool mf_ul_read_card( // Read Mifare Ultralight version if(mf_ultralight_read_version(tx_rx, reader, data)) { - // Read Signature - mf_ultralight_read_signature(tx_rx, data); + if(reader->support_signature) { + // Read Signature + mf_ultralight_read_signature(tx_rx, data); + } } if(reader->support_counters) { mf_ultralight_read_counters(tx_rx, data); @@ -231,11 +623,123 @@ static void mf_ul_protect_auth_data_on_read_command( } } +static void mf_ul_protect_auth_data_on_read_command_i2c( + uint8_t* tx_buff, + uint8_t start_page, + uint8_t end_page, + MfUltralightEmulator* emulator) { + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) { + // Blank out PWD and PACK + if(start_page <= 229 && end_page >= 229) { + uint16_t offset = (229 - start_page) * 4; + uint8_t count = 4; + if(end_page >= 230) count += 2; + memset(&tx_buff[offset], 0, count); + } + + // Handle AUTH0 for sector 0 + if(!emulator->auth_success) { + uint8_t access = emulator->data.data[228 * 4]; + if(access & 0x80) { + uint8_t auth0 = emulator->data.data[227 * 4 + 3]; + if(auth0 < end_page) { + // start_page is always < auth0; otherwise is NAK'd already + uint8_t page_offset = auth0 - start_page; + uint8_t page_count = end_page - auth0; + memset(&tx_buff[page_offset * 4], 0, page_count * 4); + } + } + } + } +} + +static void mf_ul_ntag_i2c_fill_cross_area_read( + uint8_t* tx_buff, + uint8_t start_page, + uint8_t end_page, + MfUltralightEmulator* emulator) { + // For copying config or session registers in fast read + int16_t tx_page_offset; + int16_t data_page_offset; + uint8_t page_length; + bool apply = false; + MfUltralightType type = emulator->data.type; + if(emulator->curr_sector == 0) { + if(type == MfUltralightTypeNTAGI2C1K) { + if(start_page <= 233 && end_page >= 232) { + tx_page_offset = start_page - 232; + data_page_offset = 227; + page_length = 2; + apply = true; + } + } else if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) { + if(start_page <= 237 && end_page >= 236) { + tx_page_offset = start_page - 236; + data_page_offset = 234; + page_length = 2; + apply = true; + } + } + } else if(emulator->curr_sector == 1) { + if(type == MfUltralightTypeNTAGI2C2K) { + if(start_page <= 233 && end_page >= 232) { + tx_page_offset = start_page - 232; + data_page_offset = 483; + page_length = 2; + apply = true; + } + } + } + + if(apply) { + while(tx_page_offset < 0 && page_length > 0) { + ++tx_page_offset; + ++data_page_offset; + --page_length; + } + memcpy( + &tx_buff[tx_page_offset * 4], + &emulator->data.data[data_page_offset * 4], + page_length * 4); + } +} + +static bool mf_ul_ntag_i2c_plus_check_auth( + MfUltralightEmulator* emulator, + uint8_t start_page, + bool is_write) { + if(!emulator->auth_success) { + uint8_t access = emulator->data.data[228 * 4]; + // Check NFC_PROT + if(emulator->curr_sector == 0 && ((access & 0x80) || is_write)) { + uint8_t auth0 = emulator->data.data[227 * 4 + 3]; + if(start_page >= auth0) return false; + } else if(emulator->curr_sector == 1) { + // We don't have to specifically check for type because this is done + // by address translator + uint8_t pt_i2c = emulator->data.data[231 * 4]; + // Check 2K_PROT + if(pt_i2c & 0x08) return false; + } + } + + if(emulator->curr_sector == 1) { + // Check NFC_DIS_SEC1 + uint8_t access = emulator->data.data[228 * 4]; + if(access & 0x20) return false; + } + + return true; +} + void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data) { + FURI_LOG_D(TAG, "Prepare emulation"); emulator->data = *data; emulator->auth_data = NULL; emulator->data_changed = false; emulator->comp_write_cmd_started = false; + emulator->sector_select_cmd_started = false; + emulator->ntag_i2c_plus_sector3_lockout = false; if(data->type == MfUltralightTypeUnknown) { emulator->support_fast_read = false; } else if(data->type == MfUltralightTypeUL11) { @@ -248,11 +752,15 @@ void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* d emulator->support_fast_read = true; } else if(data->type == MfUltralightTypeNTAG216) { emulator->support_fast_read = true; + } else if(data->type >= MfUltralightTypeNTAGI2C1K) { + emulator->support_fast_read = true; } - if(data->type >= MfUltralightTypeNTAG213) { + if(data->type >= MfUltralightTypeNTAG213 && data->type < MfUltralightTypeNTAGI2C1K) { uint16_t pwd_page = (data->data_size / 4) - 2; emulator->auth_data = (MfUltralightAuth*)&data->data[pwd_page * 4]; + } else if(data->type >= MfUltralightTypeNTAGI2CPlus1K) { + emulator->auth_data = (MfUltralightAuth*)&data->data[229 * 4]; } } @@ -270,6 +778,19 @@ bool mf_ul_prepare_emulation_response( uint16_t tx_bytes = 0; uint16_t tx_bits = 0; bool command_parsed = false; + bool send_ack = false; + bool respond_nothing = false; + +#ifdef FURI_DEBUG + string_t debug_buf; + string_init(debug_buf); + for(int i = 0; i < (buff_rx_len + 7) / 8; ++i) { + string_cat_printf(debug_buf, "%02x ", buff_rx[i]); + } + string_strim(debug_buf); + FURI_LOG_T(TAG, "Emu RX (%d): %s", buff_rx_len, string_get_cstr(debug_buf)); + string_reset(debug_buf); +#endif // Check composite commands if(emulator->comp_write_cmd_started) { @@ -284,6 +805,15 @@ bool mf_ul_prepare_emulation_response( command_parsed = true; } emulator->comp_write_cmd_started = false; + } else if(emulator->sector_select_cmd_started) { + if(buff_rx[0] <= 0xFE) { + emulator->curr_sector = buff_rx[0] > 3 ? 0 : buff_rx[0]; + emulator->ntag_i2c_plus_sector3_lockout = false; + command_parsed = true; + respond_nothing = true; + FURI_LOG_D(TAG, "Changing sector to %d", emulator->curr_sector); + } + emulator->sector_select_cmd_started = false; } else if(cmd == MF_UL_GET_VERSION_CMD) { if(emulator->data.type != MfUltralightTypeUnknown) { tx_bytes = sizeof(emulator->data.version); @@ -292,85 +822,189 @@ bool mf_ul_prepare_emulation_response( command_parsed = true; } } else if(cmd == MF_UL_READ_CMD) { - uint8_t start_page = buff_rx[1]; - if(start_page < page_num) { - tx_bytes = 16; - if(start_page + 4 > page_num) { - // Handle roll-over mechanism - uint8_t end_pages_num = page_num - start_page; - memcpy(buff_tx, &emulator->data.data[start_page * 4], end_pages_num * 4); - memcpy(&buff_tx[end_pages_num * 4], emulator->data.data, (4 - end_pages_num) * 4); - } else { - memcpy(buff_tx, &emulator->data.data[start_page * 4], tx_bytes); + int16_t start_page = buff_rx[1]; + tx_bytes = 16; + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + if(start_page < page_num) { + if(start_page + 4 > page_num) { + // Handle roll-over mechanism + uint8_t end_pages_num = page_num - start_page; + memcpy(buff_tx, &emulator->data.data[start_page * 4], end_pages_num * 4); + memcpy( + &buff_tx[end_pages_num * 4], emulator->data.data, (4 - end_pages_num) * 4); + } else { + memcpy(buff_tx, &emulator->data.data[start_page * 4], tx_bytes); + } + mf_ul_protect_auth_data_on_read_command( + buff_tx, start_page, (start_page + 4), emulator); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } else { + uint16_t valid_pages; + start_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, start_page, emulator->curr_sector, &valid_pages); + if(start_page != -1) { + if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K || + mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], false)) { + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K && + emulator->curr_sector == 3 && valid_pages == 1) { + // Rewind back a sector to match behavior on a real tag + --start_page; + ++valid_pages; + } + + uint16_t copy_count = (valid_pages > 4 ? 4 : valid_pages) * 4; + FURI_LOG_D( + TAG, + "NTAG I2C Emu: page valid, %02x:%02x -> %d, %d", + emulator->curr_sector, + buff_rx[1], + start_page, + valid_pages); + memcpy(buff_tx, &emulator->data.data[start_page * 4], copy_count); + // For NTAG I2C, there's no roll-over; remainder is filled by null bytes + if(copy_count < tx_bytes) + memset(&buff_tx[copy_count], 0, tx_bytes - copy_count); + // Special case: NTAG I2C Plus sector 0 page 233 read crosses into page 236 + if(start_page == 233) + memcpy(&buff_tx[12], &emulator->data.data[(start_page + 1) * 4], 4); + mf_ul_protect_auth_data_on_read_command_i2c( + buff_tx, start_page, start_page + copy_count / 4 - 1, emulator); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } else { + FURI_LOG_D( + TAG, + "NTAG I2C Emu: page invalid, %02x:%02x", + emulator->curr_sector, + buff_rx[1]); + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K && + emulator->curr_sector == 3 && !emulator->ntag_i2c_plus_sector3_lockout) { + // NTAG I2C Plus has a weird behavior where if you read sector 3 + // at an invalid address, it responds with zeroes then locks + // the read out, while if you read the mirrored session registers, + // it returns both session registers on either pages + memset(buff_tx, 0, tx_bytes); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + emulator->ntag_i2c_plus_sector3_lockout = true; + } } - mf_ul_protect_auth_data_on_read_command( - buff_tx, start_page, (start_page + 4), emulator); - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; } + if(!command_parsed) tx_bytes = 0; } else if(cmd == MF_UL_FAST_READ_CMD) { if(emulator->support_fast_read) { - uint8_t start_page = buff_rx[1]; + int16_t start_page = buff_rx[1]; uint8_t end_page = buff_rx[2]; - if((start_page < page_num) && (end_page < page_num) && (start_page < (end_page + 1))) { + if(start_page <= end_page) { tx_bytes = ((end_page + 1) - start_page) * 4; - memcpy(buff_tx, &emulator->data.data[start_page * 4], tx_bytes); - mf_ul_protect_auth_data_on_read_command(buff_tx, start_page, end_page, emulator); + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + if((start_page < page_num) && (end_page < page_num)) { + memcpy(buff_tx, &emulator->data.data[start_page * 4], tx_bytes); + mf_ul_protect_auth_data_on_read_command( + buff_tx, start_page, end_page, emulator); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } else { + uint16_t valid_pages; + start_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, start_page, emulator->curr_sector, &valid_pages); + if(start_page != -1) { + if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K || + mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], false)) { + uint16_t copy_count = (valid_pages > 4 ? 4 : valid_pages) * 4; + memcpy(buff_tx, &emulator->data.data[start_page * 4], copy_count); + if(copy_count < tx_bytes) + memset(&buff_tx[copy_count], 0, tx_bytes - copy_count); + mf_ul_ntag_i2c_fill_cross_area_read( + buff_tx, buff_rx[1], buff_rx[2], emulator); + mf_ul_protect_auth_data_on_read_command_i2c( + buff_tx, start_page, start_page + copy_count / 4 - 1, emulator); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } + } + if(!command_parsed) tx_bytes = 0; + } + } + } else if(cmd == MF_UL_WRITE) { + int16_t write_page = buff_rx[1]; + if(write_page > 1) { + uint16_t valid_pages; + write_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, write_page, emulator->curr_sector, &valid_pages); + if(write_page != -1 && + (emulator->data.type >= MfUltralightTypeNTAGI2C1K || (write_page < page_num - 2))) { + if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K || + mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], true)) { + memcpy(&emulator->data.data[write_page * 4], &buff_rx[2], 4); + emulator->data_changed = true; + send_ack = true; + command_parsed = true; + } + } + } + } else if(cmd == MF_UL_FAST_WRITE) { + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) { + if(buff_rx[1] == 0xF0 && buff_rx[2] == 0xFF) { + // TODO: update when SRAM emulation implemented + send_ack = true; + command_parsed = true; + } + } + } else if(cmd == MF_UL_COMP_WRITE) { + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + uint8_t write_page = buff_rx[1]; + if((write_page > 1) && (write_page < page_num - 2)) { + emulator->comp_write_cmd_started = true; + emulator->comp_write_page_addr = write_page; + // ACK + buff_tx[0] = 0x0A; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TXRX_RAW; + command_parsed = true; + } + } + } else if(cmd == MF_UL_READ_CNT) { + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + uint8_t cnt_num = buff_rx[1]; + if(cnt_num < 3) { + buff_tx[0] = emulator->data.counter[cnt_num] >> 16; + buff_tx[1] = emulator->data.counter[cnt_num] >> 8; + buff_tx[2] = emulator->data.counter[cnt_num]; + tx_bytes = 3; *data_type = FURI_HAL_NFC_TXRX_DEFAULT; command_parsed = true; } } - } else if(cmd == MF_UL_WRITE) { - uint8_t write_page = buff_rx[1]; - if((write_page > 1) && (write_page < page_num - 2)) { - memcpy(&emulator->data.data[write_page * 4], &buff_rx[2], 4); - emulator->data_changed = true; - // ACK - buff_tx[0] = 0x0A; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TXRX_RAW; - command_parsed = true; - } - } else if(cmd == MF_UL_COMP_WRITE) { - uint8_t write_page = buff_rx[1]; - if((write_page > 1) && (write_page < page_num - 2)) { - emulator->comp_write_cmd_started = true; - emulator->comp_write_page_addr = write_page; - // ACK - buff_tx[0] = 0x0A; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TXRX_RAW; - command_parsed = true; - } - } else if(cmd == MF_UL_READ_CNT) { - uint8_t cnt_num = buff_rx[1]; - if(cnt_num < 3) { - buff_tx[0] = emulator->data.counter[cnt_num] >> 16; - buff_tx[1] = emulator->data.counter[cnt_num] >> 8; - buff_tx[2] = emulator->data.counter[cnt_num]; - tx_bytes = 3; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } } else if(cmd == MF_UL_INC_CNT) { - uint8_t cnt_num = buff_rx[1]; - uint32_t inc = (buff_rx[2] | (buff_rx[3] << 8) | (buff_rx[4] << 16)); - if((cnt_num < 3) && (emulator->data.counter[cnt_num] + inc < 0x00FFFFFF)) { - emulator->data.counter[cnt_num] += inc; - emulator->data_changed = true; - // ACK - buff_tx[0] = 0x0A; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TXRX_RAW; - command_parsed = true; + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + uint8_t cnt_num = buff_rx[1]; + uint32_t inc = (buff_rx[2] | (buff_rx[3] << 8) | (buff_rx[4] << 16)); + if((cnt_num < 3) && (emulator->data.counter[cnt_num] + inc < 0x00FFFFFF)) { + emulator->data.counter[cnt_num] += inc; + emulator->data_changed = true; + // ACK + buff_tx[0] = 0x0A; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TXRX_RAW; + command_parsed = true; + } } } else if(cmd == MF_UL_AUTH) { - if(emulator->data.type >= MfUltralightTypeNTAG213) { + if(emulator->data.type >= MfUltralightTypeNTAG213 && + emulator->data.type != MfUltralightTypeNTAGI2C1K && + emulator->data.type != MfUltralightTypeNTAGI2C2K) { if(memcmp(&buff_rx[1], emulator->auth_data->pwd, 4) == 0) { buff_tx[0] = emulator->auth_data->pack.raw[0]; buff_tx[1] = emulator->auth_data->pack.raw[1]; tx_bytes = 2; *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + emulator->auth_success = true; command_parsed = true; } else if(!emulator->auth_data->pack.value) { buff_tx[0] = 0x80; @@ -381,36 +1015,81 @@ bool mf_ul_prepare_emulation_response( } } } else if(cmd == MF_UL_READ_SIG) { - // Check 2nd byte = 0x00 - RFU - if(buff_rx[1] == 0x00) { - tx_bytes = sizeof(emulator->data.signature); - memcpy(buff_tx, emulator->data.signature, tx_bytes); - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; + if(emulator->data.type != MfUltralightTypeNTAGI2C1K && + emulator->data.type != MfUltralightTypeNTAGI2C2K) { + // Check 2nd byte = 0x00 - RFU + if(buff_rx[1] == 0x00) { + tx_bytes = sizeof(emulator->data.signature); + memcpy(buff_tx, emulator->data.signature, tx_bytes); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } } } else if(cmd == MF_UL_CHECK_TEARING) { - uint8_t cnt_num = buff_rx[1]; - if(cnt_num < 3) { - buff_tx[0] = emulator->data.tearing[cnt_num]; - tx_bytes = 1; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + uint8_t cnt_num = buff_rx[1]; + if(cnt_num < 3) { + buff_tx[0] = emulator->data.tearing[cnt_num]; + tx_bytes = 1; + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } } } else if(cmd == MF_UL_HALT_START) { tx_bits = 0; + emulator->curr_sector = 0; + emulator->ntag_i2c_plus_sector3_lockout = false; + emulator->auth_success = false; command_parsed = true; + FURI_LOG_D(TAG, "Received HLTA"); + } else if(cmd == MF_UL_SECTOR_SELECT) { + if(emulator->data.type >= MfUltralightTypeNTAGI2C1K) { + if(buff_rx[1] == 0xFF) { + // Send ACK + emulator->sector_select_cmd_started = true; + send_ack = true; + command_parsed = true; + } + } } if(!command_parsed) { // Send NACK buff_tx[0] = 0x00; tx_bits = 4; - *data_type = FURI_HAL_NFC_TXRX_RAW; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + } else if(send_ack) { + buff_tx[0] = 0x0A; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; } - // Return tx buffer size in bits - if(tx_bytes) { - tx_bits = tx_bytes * 8; + + if(respond_nothing) { + *buff_tx_len = UINT16_MAX; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + } else { + // Return tx buffer size in bits + if(tx_bytes) { + tx_bits = tx_bytes * 8; + } + *buff_tx_len = tx_bits; } - *buff_tx_len = tx_bits; + +#ifdef FURI_DEBUG + if(*buff_tx_len == UINT16_MAX) { + FURI_LOG_T(TAG, "Emu TX: no reply"); + } else if(*buff_tx_len > 0) { + int count = (*buff_tx_len + 7) / 8; + for(int i = 0; i < count; ++i) { + string_cat_printf(debug_buf, "%02x ", buff_tx[i]); + } + string_strim(debug_buf); + FURI_LOG_T(TAG, "Emu TX (%d): %s", *buff_tx_len, string_get_cstr(debug_buf)); + string_clear(debug_buf); + } else { + FURI_LOG_T(TAG, "Emu TX: HALT"); + } +#endif + return tx_bits > 0; } diff --git a/lib/nfc_protocols/mifare_ultralight.h b/lib/nfc_protocols/mifare_ultralight.h index 858fc3ec..76dc2edb 100644 --- a/lib/nfc_protocols/mifare_ultralight.h +++ b/lib/nfc_protocols/mifare_ultralight.h @@ -2,7 +2,8 @@ #include -#define MF_UL_MAX_DUMP_SIZE 1024 +// Largest tag is NTAG I2C Plus 2K, both data sectors plus SRAM +#define MF_UL_MAX_DUMP_SIZE ((238 + 256 + 16) * 4) #define MF_UL_TEARING_FLAG_DEFAULT (0xBD) @@ -11,6 +12,7 @@ #define MF_UL_READ_CMD (0x30) #define MF_UL_FAST_READ_CMD (0x3A) #define MF_UL_WRITE (0xA2) +#define MF_UL_FAST_WRITE (0xA6) #define MF_UL_COMP_WRITE (0xA0) #define MF_UL_READ_CNT (0x39) #define MF_UL_INC_CNT (0xA5) @@ -18,6 +20,7 @@ #define MF_UL_READ_SIG (0x3C) #define MF_UL_CHECK_TEARING (0x3E) #define MF_UL_READ_VCSL (0x4B) +#define MF_UL_SECTOR_SELECT (0xC2) typedef enum { MfUltralightTypeUnknown, @@ -26,6 +29,10 @@ typedef enum { MfUltralightTypeNTAG213, MfUltralightTypeNTAG215, MfUltralightTypeNTAG216, + MfUltralightTypeNTAGI2C1K, + MfUltralightTypeNTAGI2C2K, + MfUltralightTypeNTAGI2CPlus1K, + MfUltralightTypeNTAGI2CPlus2K, // Keep last for number of types calculation MfUltralightTypeNum, @@ -71,11 +78,12 @@ typedef struct { } MfUltralightAuth; typedef struct { - uint8_t pages_to_read; - uint8_t pages_read; + uint16_t pages_to_read; + int16_t pages_read; bool support_fast_read; bool support_tearing_flags; bool support_counters; + bool support_signature; } MfUltralightReader; typedef struct { @@ -85,6 +93,10 @@ typedef struct { bool comp_write_cmd_started; uint8_t comp_write_page_addr; MfUltralightAuth* auth_data; + bool auth_success; + uint8_t curr_sector; + bool sector_select_cmd_started; + bool ntag_i2c_plus_sector3_lockout; } MfUltralightEmulator; bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); From 533f12af15ba6f04fa246d3408d5f55f69179c27 Mon Sep 17 00:00:00 2001 From: Gary Date: Thu, 26 May 2022 09:00:59 -0400 Subject: [PATCH 19/28] Change "FuriHalNfc Current state" trace to only log on change of state #1272 Co-authored-by: gornekich --- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index d1bdb5d7..00c96ec3 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -61,6 +61,7 @@ bool furi_hal_nfc_detect(FuriHalNfcDevData* nfc_data, uint32_t timeout) { rfalLowPowerModeStop(); rfalNfcState state = rfalNfcGetState(); + rfalNfcState state_old = 0; if(state == RFAL_NFC_STATE_NOTINIT) { rfalNfcInitialize(); } @@ -83,11 +84,14 @@ bool furi_hal_nfc_detect(FuriHalNfcDevData* nfc_data, uint32_t timeout) { while(true) { rfalNfcWorker(); state = rfalNfcGetState(); + if(state != state_old) { + FURI_LOG_T(TAG, "State change %d -> %d", state_old, state); + } + state_old = state; if(state == RFAL_NFC_STATE_ACTIVATED) { detected = true; break; } - FURI_LOG_T(TAG, "Current state %d", state); if(state == RFAL_NFC_STATE_POLL_ACTIVATION) { start = DWT->CYCCNT; continue; From 79920a3522092f0a7ad0bb043ba096119ef8057d Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 27 May 2022 14:19:21 +0300 Subject: [PATCH 20/28] [FL-2491] File browser GUI module (#1237) * File browser module and test app * nfc: Add support for saved files in subdirectories * nfc: Use helper function to get shadow path when loading data * File browser dialog integration pt.1 * File browser dialog integration pt.2 * Gui,Dialogs: drop file select * Correct use of dynamic string_t(string_ptr) Co-authored-by: Yukai Li Co-authored-by: Aleksandr Kutuzov --- applications/applications.c | 9 + applications/applications.mk | 6 + applications/bad_usb/bad_usb_app.c | 37 +- applications/bad_usb/bad_usb_app_i.h | 3 +- .../scenes/bad_usb_scene_file_select.c | 14 +- .../bad_usb/scenes/bad_usb_scene_work.c | 9 +- applications/bad_usb/views/bad_usb_view.c | 8 +- applications/bad_usb/views/bad_usb_view.h | 2 +- .../file_browser_test/file_browser_app.c | 99 ++++ .../file_browser_test/file_browser_app_i.h | 32 ++ .../scenes/file_browser_scene.c | 30 + .../scenes/file_browser_scene.h | 29 + .../scenes/file_browser_scene_browser.c | 43 ++ .../scenes/file_browser_scene_config.h | 3 + .../scenes/file_browser_scene_result.c | 36 ++ .../scenes/file_browser_scene_start.c | 44 ++ applications/dialogs/dialogs.c | 7 +- applications/dialogs/dialogs.h | 25 +- applications/dialogs/dialogs_api.c | 29 +- applications/dialogs/dialogs_message.h | 16 +- .../dialogs/dialogs_module_file_browser.c | 59 ++ ...select.h => dialogs_module_file_browser.h} | 2 +- .../dialogs/dialogs_module_file_select.c | 59 -- applications/gui/modules/file_browser.c | 534 ++++++++++++++++++ applications/gui/modules/file_browser.h | 39 ++ .../gui/modules/file_browser_worker.c | 408 +++++++++++++ .../gui/modules/file_browser_worker.h | 55 ++ applications/gui/modules/file_select.c | 475 ---------------- applications/gui/modules/file_select.h | 31 - applications/gui/modules/validators.c | 5 +- applications/ibutton/ibutton.c | 91 ++- applications/ibutton/ibutton_i.h | 3 +- .../ibutton/scenes/ibutton_scene_add_type.c | 3 +- .../scenes/ibutton_scene_delete_confirm.c | 9 +- .../ibutton/scenes/ibutton_scene_emulate.c | 14 +- .../ibutton/scenes/ibutton_scene_info.c | 9 +- .../ibutton/scenes/ibutton_scene_read.c | 2 +- .../ibutton/scenes/ibutton_scene_save_name.c | 24 +- .../ibutton/scenes/ibutton_scene_start.c | 1 + .../ibutton/scenes/ibutton_scene_write.c | 15 +- applications/infrared/infrared_app.cpp | 17 +- applications/infrared/infrared_app.h | 2 + .../infrared/infrared_app_remote_manager.cpp | 130 +++-- .../infrared/infrared_app_remote_manager.h | 27 +- .../scene/infrared_app_scene_edit_rename.cpp | 13 +- .../scene/infrared_app_scene_remote_list.cpp | 19 +- .../scene/infrared_app_scene_start.cpp | 2 + applications/lfrfid/lfrfid_app.cpp | 64 +-- applications/lfrfid/lfrfid_app.h | 7 +- .../scene/lfrfid_app_scene_save_name.cpp | 12 +- applications/music_player/music_player.c | 20 +- applications/nfc/nfc_device.c | 116 ++-- applications/nfc/nfc_device.h | 7 +- applications/nfc/scenes/nfc_scene_delete.c | 2 +- applications/nfc/scenes/nfc_scene_save_name.c | 19 +- .../nfc/scenes/nfc_scene_save_success.c | 3 - .../nfc/scenes/nfc_scene_saved_menu.c | 2 +- applications/nfc/scenes/nfc_scene_set_type.c | 2 + .../subghz/scenes/subghz_scene_delete.c | 2 +- .../subghz/scenes/subghz_scene_delete_raw.c | 4 +- .../subghz/scenes/subghz_scene_more_raw.c | 2 +- .../subghz/scenes/subghz_scene_read_raw.c | 8 +- .../subghz/scenes/subghz_scene_save_name.c | 52 +- .../subghz/scenes/subghz_scene_set_type.c | 6 +- .../subghz/scenes/subghz_scene_transmitter.c | 2 +- applications/subghz/subghz.c | 16 +- applications/subghz/subghz_cli.c | 2 +- applications/subghz/subghz_history.c | 4 +- applications/subghz/subghz_i.c | 52 +- applications/subghz/subghz_i.h | 10 +- applications/subghz/views/receiver.c | 6 +- applications/subghz/views/subghz_read_raw.c | 16 +- applications/subghz/views/transmitter.c | 6 +- assets/compiled/assets_icons.c | 12 + assets/compiled/assets_icons.h | 3 + assets/icons/Archive/back_10px.png | Bin 0 -> 154 bytes assets/icons/Archive/loading_10px.png | Bin 0 -> 173 bytes assets/icons/Archive/music_10px.png | Bin 0 -> 142 bytes lib/one_wire/ibutton/ibutton_key.c | 9 - lib/one_wire/ibutton/ibutton_key.h | 14 - lib/toolbox/path.c | 14 + lib/toolbox/path.h | 9 + 82 files changed, 2025 insertions(+), 1007 deletions(-) create mode 100644 applications/debug_tools/file_browser_test/file_browser_app.c create mode 100644 applications/debug_tools/file_browser_test/file_browser_app_i.h create mode 100644 applications/debug_tools/file_browser_test/scenes/file_browser_scene.c create mode 100644 applications/debug_tools/file_browser_test/scenes/file_browser_scene.h create mode 100644 applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c create mode 100644 applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h create mode 100644 applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c create mode 100644 applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c create mode 100644 applications/dialogs/dialogs_module_file_browser.c rename applications/dialogs/{dialogs_module_file_select.h => dialogs_module_file_browser.h} (54%) delete mode 100644 applications/dialogs/dialogs_module_file_select.c create mode 100644 applications/gui/modules/file_browser.c create mode 100644 applications/gui/modules/file_browser.h create mode 100644 applications/gui/modules/file_browser_worker.c create mode 100644 applications/gui/modules/file_browser_worker.h delete mode 100644 applications/gui/modules/file_select.c delete mode 100644 applications/gui/modules/file_select.h mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_save_name.c create mode 100644 assets/icons/Archive/back_10px.png create mode 100644 assets/icons/Archive/loading_10px.png create mode 100644 assets/icons/Archive/music_10px.png diff --git a/applications/applications.c b/applications/applications.c index 4deae885..2ed1b961 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -44,6 +44,7 @@ extern int32_t vibro_test_app(void* p); extern int32_t bt_hid_app(void* p); extern int32_t battery_test_app(void* p); extern int32_t text_box_test_app(void* p); +extern int32_t file_browser_app(void* p); // Plugins extern int32_t music_player_app(void* p); @@ -459,6 +460,14 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { .flags = FlipperApplicationFlagDefault}, #endif +#ifdef APP_FILE_BROWSER_TEST + {.app = file_browser_app, + .name = "File Browser test", + .stack_size = 2048, + .icon = &A_BadUsb_14, + .flags = FlipperApplicationFlagDefault}, +#endif + #ifdef APP_BATTERY_TEST {.app = battery_test_app, .name = "Battery Test", diff --git a/applications/applications.mk b/applications/applications.mk index efe6aa42..e7a9ebfc 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -62,6 +62,7 @@ APP_USB_MOUSE = 1 APP_BAD_USB = 1 APP_U2F = 1 APP_UART_ECHO = 1 +APP_FILE_BROWSER_TEST = 1 endif @@ -207,6 +208,11 @@ CFLAGS += -DAPP_KEYPAD_TEST SRV_GUI = 1 endif +APP_FILE_BROWSER_TEST ?= 0 +ifeq ($(APP_FILE_BROWSER_TEST), 1) +CFLAGS += -DAPP_FILE_BROWSER_TEST +SRV_GUI = 1 +endif APP_ACCESSOR ?= 0 ifeq ($(APP_ACCESSOR), 1) diff --git a/applications/bad_usb/bad_usb_app.c b/applications/bad_usb/bad_usb_app.c index eb647e00..65ccc575 100644 --- a/applications/bad_usb/bad_usb_app.c +++ b/applications/bad_usb/bad_usb_app.c @@ -1,4 +1,5 @@ #include "bad_usb_app_i.h" +#include "m-string.h" #include #include #include @@ -22,33 +23,13 @@ static void bad_usb_app_tick_event_callback(void* context) { scene_manager_handle_tick_event(app->scene_manager); } -static bool bad_usb_check_assets() { - Storage* fs_api = furi_record_open("storage"); - - File* dir = storage_file_alloc(fs_api); - bool ret = false; - - if(storage_dir_open(dir, BAD_USB_APP_PATH_FOLDER)) { - ret = true; - } - - storage_dir_close(dir); - storage_file_free(dir); - - furi_record_close("storage"); - - return ret; -} - BadUsbApp* bad_usb_app_alloc(char* arg) { BadUsbApp* app = malloc(sizeof(BadUsbApp)); + string_init(app->file_path); + if(arg != NULL) { - string_t filename; - string_init(filename); - path_extract_filename_no_ext(arg, filename); - strncpy(app->file_name, string_get_cstr(filename), BAD_USB_FILE_NAME_LEN); - string_clear(filename); + string_set_str(app->file_path, arg); } app->gui = furi_record_open("gui"); @@ -83,13 +64,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { app->error = BadUsbAppErrorCloseRpc; scene_manager_next_scene(app->scene_manager, BadUsbSceneError); } else { - if(*app->file_name != '\0') { + if(!string_empty_p(app->file_path)) { scene_manager_next_scene(app->scene_manager, BadUsbSceneWork); - } else if(bad_usb_check_assets()) { - scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); } else { - app->error = BadUsbAppErrorNoFiles; - scene_manager_next_scene(app->scene_manager, BadUsbSceneError); + string_set_str(app->file_path, BAD_USB_APP_PATH_FOLDER); + scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); } } @@ -117,6 +96,8 @@ void bad_usb_app_free(BadUsbApp* app) { furi_record_close("notification"); furi_record_close("dialogs"); + string_clear(app->file_path); + free(app); } diff --git a/applications/bad_usb/bad_usb_app_i.h b/applications/bad_usb/bad_usb_app_i.h index 67f5816b..c82419e0 100644 --- a/applications/bad_usb/bad_usb_app_i.h +++ b/applications/bad_usb/bad_usb_app_i.h @@ -16,7 +16,6 @@ #define BAD_USB_APP_PATH_FOLDER "/any/badusb" #define BAD_USB_APP_EXTENSION ".txt" -#define BAD_USB_FILE_NAME_LEN 40 typedef enum { BadUsbAppErrorNoFiles, @@ -32,7 +31,7 @@ struct BadUsbApp { Widget* widget; BadUsbAppError error; - char file_name[BAD_USB_FILE_NAME_LEN + 1]; + string_t file_path; BadUsb* bad_usb_view; BadUsbScript* bad_usb_script; }; diff --git a/applications/bad_usb/scenes/bad_usb_scene_file_select.c b/applications/bad_usb/scenes/bad_usb_scene_file_select.c index 82f03bab..1e6ba895 100644 --- a/applications/bad_usb/scenes/bad_usb_scene_file_select.c +++ b/applications/bad_usb/scenes/bad_usb_scene_file_select.c @@ -5,14 +5,16 @@ static bool bad_usb_file_select(BadUsbApp* bad_usb) { furi_assert(bad_usb); - // Input events and views are managed by file_select - bool res = dialog_file_select_show( + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( bad_usb->dialogs, - BAD_USB_APP_PATH_FOLDER, + bad_usb->file_path, + bad_usb->file_path, BAD_USB_APP_EXTENSION, - bad_usb->file_name, - sizeof(bad_usb->file_name), - NULL); + true, + &I_badusb_10px, + true); + return res; } diff --git a/applications/bad_usb/scenes/bad_usb_scene_work.c b/applications/bad_usb/scenes/bad_usb_scene_work.c index a3a46803..516cbde3 100644 --- a/applications/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/bad_usb/scenes/bad_usb_scene_work.c @@ -2,6 +2,8 @@ #include "../bad_usb_app_i.h" #include "../views/bad_usb_view.h" #include "furi_hal.h" +#include "m-string.h" +#include "toolbox/path.h" void bad_usb_scene_work_ok_callback(InputType type, void* context) { furi_assert(context); @@ -28,10 +30,9 @@ void bad_usb_scene_work_on_enter(void* context) { string_t file_name; string_init(file_name); - bad_usb_set_file_name(app->bad_usb_view, app->file_name); - string_printf( - file_name, "%s/%s%s", BAD_USB_APP_PATH_FOLDER, app->file_name, BAD_USB_APP_EXTENSION); - app->bad_usb_script = bad_usb_script_open(file_name); + path_extract_filename(app->file_path, file_name, true); + bad_usb_set_file_name(app->bad_usb_view, string_get_cstr(file_name)); + app->bad_usb_script = bad_usb_script_open(app->file_path); string_clear(file_name); diff --git a/applications/bad_usb/views/bad_usb_view.c b/applications/bad_usb/views/bad_usb_view.c index 5b6fe6e8..430885df 100644 --- a/applications/bad_usb/views/bad_usb_view.c +++ b/applications/bad_usb/views/bad_usb_view.c @@ -2,6 +2,8 @@ #include "../bad_usb_script.h" #include +#define MAX_NAME_LEN 64 + struct BadUsb { View* view; BadUsbOkCallback callback; @@ -9,7 +11,7 @@ struct BadUsb { }; typedef struct { - char* file_name; + char file_name[MAX_NAME_LEN]; BadUsbState state; uint8_t anim_frame; } BadUsbModel; @@ -149,11 +151,11 @@ void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* c }); } -void bad_usb_set_file_name(BadUsb* bad_usb, char* name) { +void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) { furi_assert(name); with_view_model( bad_usb->view, (BadUsbModel * model) { - model->file_name = name; + strncpy(model->file_name, name, MAX_NAME_LEN); return true; }); } diff --git a/applications/bad_usb/views/bad_usb_view.h b/applications/bad_usb/views/bad_usb_view.h index f5a8a0fa..80a47e2c 100755 --- a/applications/bad_usb/views/bad_usb_view.h +++ b/applications/bad_usb/views/bad_usb_view.h @@ -14,6 +14,6 @@ View* bad_usb_get_view(BadUsb* bad_usb); void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context); -void bad_usb_set_file_name(BadUsb* bad_usb, char* name); +void bad_usb_set_file_name(BadUsb* bad_usb, const char* name); void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); diff --git a/applications/debug_tools/file_browser_test/file_browser_app.c b/applications/debug_tools/file_browser_test/file_browser_app.c new file mode 100644 index 00000000..c9b63ecb --- /dev/null +++ b/applications/debug_tools/file_browser_test/file_browser_app.c @@ -0,0 +1,99 @@ +#include "assets_icons.h" +#include "file_browser_app_i.h" +#include "gui/modules/file_browser.h" +#include "m-string.h" +#include +#include +#include +#include + +static bool file_browser_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + FileBrowserApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool file_browser_app_back_event_callback(void* context) { + furi_assert(context); + FileBrowserApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void file_browser_app_tick_event_callback(void* context) { + furi_assert(context); + FileBrowserApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +FileBrowserApp* file_browser_app_alloc(char* arg) { + UNUSED(arg); + FileBrowserApp* app = malloc(sizeof(FileBrowserApp)); + + app->gui = furi_record_open("gui"); + app->dialogs = furi_record_open("dialogs"); + + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + + app->scene_manager = scene_manager_alloc(&file_browser_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, file_browser_app_tick_event_callback, 500); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, file_browser_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, file_browser_app_back_event_callback); + + app->widget = widget_alloc(); + + string_init(app->file_path); + app->file_browser = file_browser_alloc(app->file_path); + file_browser_configure(app->file_browser, "*", true, &I_badusb_10px, true); + + view_dispatcher_add_view( + app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget)); + view_dispatcher_add_view( + app->view_dispatcher, FileBrowserAppViewResult, widget_get_view(app->widget)); + view_dispatcher_add_view( + app->view_dispatcher, FileBrowserAppViewBrowser, file_browser_get_view(app->file_browser)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + scene_manager_next_scene(app->scene_manager, FileBrowserSceneStart); + + return app; +} + +void file_browser_app_free(FileBrowserApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewStart); + view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewResult); + view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewBrowser); + widget_free(app->widget); + file_browser_free(app->file_browser); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close("gui"); + furi_record_close("notification"); + furi_record_close("dialogs"); + + string_clear(app->file_path); + + free(app); +} + +int32_t file_browser_app(void* p) { + FileBrowserApp* file_browser_app = file_browser_app_alloc((char*)p); + + view_dispatcher_run(file_browser_app->view_dispatcher); + + file_browser_app_free(file_browser_app); + return 0; +} diff --git a/applications/debug_tools/file_browser_test/file_browser_app_i.h b/applications/debug_tools/file_browser_test/file_browser_app_i.h new file mode 100644 index 00000000..6e8412c9 --- /dev/null +++ b/applications/debug_tools/file_browser_test/file_browser_app_i.h @@ -0,0 +1,32 @@ +#pragma once + +#include "scenes/file_browser_scene.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct FileBrowserApp FileBrowserApp; + +struct FileBrowserApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + DialogsApp* dialogs; + Widget* widget; + FileBrowser* file_browser; + + string_t file_path; +}; + +typedef enum { + FileBrowserAppViewStart, + FileBrowserAppViewBrowser, + FileBrowserAppViewResult, +} FileBrowserAppView; diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene.c b/applications/debug_tools/file_browser_test/scenes/file_browser_scene.c new file mode 100644 index 00000000..72a6e84d --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene.c @@ -0,0 +1,30 @@ +#include "file_browser_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const file_browser_scene_on_enter_handlers[])(void*) = { +#include "file_browser_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const file_browser_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "file_browser_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const file_browser_scene_on_exit_handlers[])(void* context) = { +#include "file_browser_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers file_browser_scene_handlers = { + .on_enter_handlers = file_browser_scene_on_enter_handlers, + .on_event_handlers = file_browser_scene_on_event_handlers, + .on_exit_handlers = file_browser_scene_on_exit_handlers, + .scene_num = FileBrowserSceneNum, +}; diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene.h b/applications/debug_tools/file_browser_test/scenes/file_browser_scene.h new file mode 100644 index 00000000..d690fca9 --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) FileBrowserScene##id, +typedef enum { +#include "file_browser_scene_config.h" + FileBrowserSceneNum, +} FileBrowserScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers file_browser_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "file_browser_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "file_browser_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "file_browser_scene_config.h" +#undef ADD_SCENE diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c new file mode 100644 index 00000000..ca16ad0d --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c @@ -0,0 +1,43 @@ +#include "../file_browser_app_i.h" +#include "furi/check.h" +#include "furi/log.h" +#include "furi_hal.h" +#include "m-string.h" + +#define DEFAULT_PATH "/" +#define EXTENSION "*" + +bool file_browser_scene_browser_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + FileBrowserApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_next_scene(app->scene_manager, FileBrowserSceneResult); + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + } + return consumed; +} + +static void file_browser_callback(void* context) { + FileBrowserApp* app = context; + furi_assert(app); + view_dispatcher_send_custom_event(app->view_dispatcher, SceneManagerEventTypeCustom); +} + +void file_browser_scene_browser_on_enter(void* context) { + FileBrowserApp* app = context; + + file_browser_set_callback(app->file_browser, file_browser_callback, app); + + file_browser_start(app->file_browser, app->file_path); + + view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewBrowser); +} + +void file_browser_scene_browser_on_exit(void* context) { + FileBrowserApp* app = context; + + file_browser_stop(app->file_browser); +} diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h new file mode 100644 index 00000000..6597df3a --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(file_browser, start, Start) +ADD_SCENE(file_browser, browser, Browser) +ADD_SCENE(file_browser, result, Result) diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c new file mode 100644 index 00000000..53576cef --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c @@ -0,0 +1,36 @@ +#include "../file_browser_app_i.h" +#include "furi_hal.h" +#include "m-string.h" + +void file_browser_scene_result_ok_callback(InputType type, void* context) { + furi_assert(context); + FileBrowserApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, type); +} + +bool file_browser_scene_result_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + //FileBrowserApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + } + return consumed; +} + +void file_browser_scene_result_on_enter(void* context) { + FileBrowserApp* app = context; + + widget_add_string_multiline_element( + app->widget, 64, 10, AlignCenter, AlignTop, FontSecondary, string_get_cstr(app->file_path)); + + view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewResult); +} + +void file_browser_scene_result_on_exit(void* context) { + UNUSED(context); + FileBrowserApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c new file mode 100644 index 00000000..bb71e83d --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c @@ -0,0 +1,44 @@ +#include "../file_browser_app_i.h" +#include "furi_hal.h" +#include "gui/modules/widget_elements/widget_element_i.h" + +static void + file_browser_scene_start_ok_callback(GuiButtonType result, InputType type, void* context) { + UNUSED(result); + furi_assert(context); + FileBrowserApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, type); + } +} + +bool file_browser_scene_start_on_event(void* context, SceneManagerEvent event) { + FileBrowserApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + string_set_str(app->file_path, "/any/badusb/demo_windows.txt"); + scene_manager_next_scene(app->scene_manager, FileBrowserSceneBrowser); + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + } + return consumed; +} + +void file_browser_scene_start_on_enter(void* context) { + FileBrowserApp* app = context; + + widget_add_string_multiline_element( + app->widget, 64, 20, AlignCenter, AlignTop, FontSecondary, "Press OK to start"); + + widget_add_button_element( + app->widget, GuiButtonTypeCenter, "Ok", file_browser_scene_start_ok_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewStart); +} + +void file_browser_scene_start_on_exit(void* context) { + UNUSED(context); + FileBrowserApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/dialogs/dialogs.c b/applications/dialogs/dialogs.c index cf4a2ad6..8929dc11 100644 --- a/applications/dialogs/dialogs.c +++ b/applications/dialogs/dialogs.c @@ -1,6 +1,7 @@ +#include "dialogs/dialogs_message.h" #include "dialogs_i.h" #include "dialogs_api_lock.h" -#include "dialogs_module_file_select.h" +#include "dialogs_module_file_browser.h" #include "dialogs_module_message.h" static DialogsApp* dialogs_app_alloc() { @@ -13,9 +14,9 @@ static DialogsApp* dialogs_app_alloc() { static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* message) { UNUSED(app); switch(message->command) { - case DialogsAppCommandFileOpen: + case DialogsAppCommandFileBrowser: message->return_data->bool_value = - dialogs_app_process_module_file_select(&message->data->file_select); + dialogs_app_process_module_file_browser(&message->data->file_browser); break; case DialogsAppCommandDialog: message->return_data->dialog_value = diff --git a/applications/dialogs/dialogs.h b/applications/dialogs/dialogs.h index 9c71c098..53606056 100644 --- a/applications/dialogs/dialogs.h +++ b/applications/dialogs/dialogs.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include "m-string.h" #ifdef __cplusplus extern "C" { @@ -10,25 +11,27 @@ extern "C" { typedef struct DialogsApp DialogsApp; -/****************** FILE SELECT ******************/ +/****************** FILE BROWSER ******************/ /** - * Shows and processes the file selection dialog + * Shows and processes the file browser dialog * @param context api pointer - * @param path path to directory + * @param result_path selected file path string pointer + * @param path preselected file path string pointer * @param extension file extension to be offered for selection - * @param selected_filename buffer where the selected filename will be saved - * @param selected_filename_size and the size of this buffer - * @param preselected_filename filename to be preselected + * @param skip_assets true - do not show assets folders + * @param icon file icon pointer, NULL for default icon + * @param hide_ext true - hide extensions for files * @return bool whether a file was selected */ -bool dialog_file_select_show( +bool dialog_file_browser_show( DialogsApp* context, - const char* path, + string_ptr result_path, + string_ptr path, const char* extension, - char* result, - uint8_t result_size, - const char* preselected_filename); + bool skip_assets, + const Icon* icon, + bool hide_ext); /****************** MESSAGE ******************/ diff --git a/applications/dialogs/dialogs_api.c b/applications/dialogs/dialogs_api.c index c4efb287..fab3a5ea 100644 --- a/applications/dialogs/dialogs_api.c +++ b/applications/dialogs/dialogs_api.c @@ -1,31 +1,36 @@ +#include "dialogs/dialogs_message.h" #include "dialogs_i.h" #include "dialogs_api_lock.h" +#include "m-string.h" -/****************** File select ******************/ +/****************** File browser ******************/ -bool dialog_file_select_show( +bool dialog_file_browser_show( DialogsApp* context, - const char* path, + string_ptr result_path, + string_ptr path, const char* extension, - char* result, - uint8_t result_size, - const char* preselected_filename) { + bool skip_assets, + const Icon* icon, + bool hide_ext) { FuriApiLock lock = API_LOCK_INIT_LOCKED(); furi_check(lock != NULL); DialogsAppData data = { - .file_select = { - .path = path, + .file_browser = { .extension = extension, - .result = result, - .result_size = result_size, - .preselected_filename = preselected_filename, + .result_path = result_path, + .file_icon = icon, + .hide_ext = hide_ext, + .skip_assets = skip_assets, + .preselected_filename = path, + }}; DialogsAppReturn return_data; DialogsAppMessage message = { .lock = lock, - .command = DialogsAppCommandFileOpen, + .command = DialogsAppCommandFileBrowser, .data = &data, .return_data = &return_data, }; diff --git a/applications/dialogs/dialogs_message.h b/applications/dialogs/dialogs_message.h index d7b5fabf..ccfbdece 100644 --- a/applications/dialogs/dialogs_message.h +++ b/applications/dialogs/dialogs_message.h @@ -2,25 +2,27 @@ #include #include "dialogs_i.h" #include "dialogs_api_lock.h" +#include "m-string.h" #ifdef __cplusplus extern "C" { #endif typedef struct { - const char* path; const char* extension; - char* result; - uint8_t result_size; - const char* preselected_filename; -} DialogsAppMessageDataFileSelect; + bool skip_assets; + bool hide_ext; + const Icon* file_icon; + string_ptr result_path; + string_ptr preselected_filename; +} DialogsAppMessageDataFileBrowser; typedef struct { const DialogMessage* message; } DialogsAppMessageDataDialog; typedef union { - DialogsAppMessageDataFileSelect file_select; + DialogsAppMessageDataFileBrowser file_browser; DialogsAppMessageDataDialog dialog; } DialogsAppData; @@ -30,7 +32,7 @@ typedef union { } DialogsAppReturn; typedef enum { - DialogsAppCommandFileOpen, + DialogsAppCommandFileBrowser, DialogsAppCommandDialog, } DialogsAppCommand; diff --git a/applications/dialogs/dialogs_module_file_browser.c b/applications/dialogs/dialogs_module_file_browser.c new file mode 100644 index 00000000..ecd0ca79 --- /dev/null +++ b/applications/dialogs/dialogs_module_file_browser.c @@ -0,0 +1,59 @@ +#include "dialogs_i.h" +#include "dialogs_api_lock.h" +#include "gui/modules/file_browser.h" + +typedef struct { + FuriApiLock lock; + bool result; +} DialogsAppFileBrowserContext; + +static void dialogs_app_file_browser_back_callback(void* context) { + furi_assert(context); + DialogsAppFileBrowserContext* file_browser_context = context; + file_browser_context->result = false; + API_LOCK_UNLOCK(file_browser_context->lock); +} + +static void dialogs_app_file_browser_callback(void* context) { + furi_assert(context); + DialogsAppFileBrowserContext* file_browser_context = context; + file_browser_context->result = true; + API_LOCK_UNLOCK(file_browser_context->lock); +} + +bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data) { + bool ret = false; + Gui* gui = furi_record_open("gui"); + + DialogsAppFileBrowserContext* file_browser_context = + malloc(sizeof(DialogsAppFileBrowserContext)); + file_browser_context->lock = API_LOCK_INIT_LOCKED(); + + ViewHolder* view_holder = view_holder_alloc(); + view_holder_attach_to_gui(view_holder, gui); + view_holder_set_back_callback( + view_holder, dialogs_app_file_browser_back_callback, file_browser_context); + + FileBrowser* file_browser = file_browser_alloc(data->result_path); + file_browser_set_callback( + file_browser, dialogs_app_file_browser_callback, file_browser_context); + file_browser_configure( + file_browser, data->extension, data->skip_assets, data->file_icon, data->hide_ext); + file_browser_start(file_browser, data->preselected_filename); + + view_holder_set_view(view_holder, file_browser_get_view(file_browser)); + view_holder_start(view_holder); + API_LOCK_WAIT_UNTIL_UNLOCK(file_browser_context->lock); + + ret = file_browser_context->result; + + view_holder_stop(view_holder); + view_holder_free(view_holder); + file_browser_stop(file_browser); + file_browser_free(file_browser); + API_LOCK_FREE(file_browser_context->lock); + free(file_browser_context); + furi_record_close("gui"); + + return ret; +} diff --git a/applications/dialogs/dialogs_module_file_select.h b/applications/dialogs/dialogs_module_file_browser.h similarity index 54% rename from applications/dialogs/dialogs_module_file_select.h rename to applications/dialogs/dialogs_module_file_browser.h index 749fe9c1..b6cbdf6a 100644 --- a/applications/dialogs/dialogs_module_file_select.h +++ b/applications/dialogs/dialogs_module_file_browser.h @@ -5,7 +5,7 @@ extern "C" { #endif -bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data); +bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data); #ifdef __cplusplus } diff --git a/applications/dialogs/dialogs_module_file_select.c b/applications/dialogs/dialogs_module_file_select.c deleted file mode 100644 index f133c00a..00000000 --- a/applications/dialogs/dialogs_module_file_select.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "dialogs_i.h" -#include "dialogs_api_lock.h" -#include - -typedef struct { - FuriApiLock lock; - bool result; -} DialogsAppFileSelectContext; - -static void dialogs_app_file_select_back_callback(void* context) { - furi_assert(context); - DialogsAppFileSelectContext* file_select_context = context; - file_select_context->result = false; - API_LOCK_UNLOCK(file_select_context->lock); -} - -static void dialogs_app_file_select_callback(bool result, void* context) { - furi_assert(context); - DialogsAppFileSelectContext* file_select_context = context; - file_select_context->result = result; - API_LOCK_UNLOCK(file_select_context->lock); -} - -bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data) { - bool ret = false; - Gui* gui = furi_record_open("gui"); - - DialogsAppFileSelectContext* file_select_context = malloc(sizeof(DialogsAppFileSelectContext)); - file_select_context->lock = API_LOCK_INIT_LOCKED(); - - ViewHolder* view_holder = view_holder_alloc(); - view_holder_attach_to_gui(view_holder, gui); - view_holder_set_back_callback( - view_holder, dialogs_app_file_select_back_callback, file_select_context); - - FileSelect* file_select = file_select_alloc(); - file_select_set_callback(file_select, dialogs_app_file_select_callback, file_select_context); - file_select_set_filter(file_select, data->path, data->extension); - file_select_set_result_buffer(file_select, data->result, data->result_size); - file_select_init(file_select); - if(data->preselected_filename != NULL) { - file_select_set_selected_file(file_select, data->preselected_filename); - } - - view_holder_set_view(view_holder, file_select_get_view(file_select)); - view_holder_start(view_holder); - API_LOCK_WAIT_UNTIL_UNLOCK(file_select_context->lock); - - ret = file_select_context->result; - - view_holder_stop(view_holder); - view_holder_free(view_holder); - file_select_free(file_select); - API_LOCK_FREE(file_select_context->lock); - free(file_select_context); - furi_record_close("gui"); - - return ret; -} diff --git a/applications/gui/modules/file_browser.c b/applications/gui/modules/file_browser.c new file mode 100644 index 00000000..1cef1d07 --- /dev/null +++ b/applications/gui/modules/file_browser.c @@ -0,0 +1,534 @@ +#include "file_browser.h" +#include "assets_icons.h" +#include "cmsis_os2.h" +#include "file_browser_worker.h" +#include "furi/check.h" +#include "furi/common_defines.h" +#include "furi/log.h" +#include "furi_hal_resources.h" +#include "m-string.h" +#include +#include +#include +#include "toolbox/path.h" + +#define LIST_ITEMS 5u +#define MAX_LEN_PX 110 +#define FRAME_HEIGHT 12 +#define Y_OFFSET 3 + +#define ITEM_LIST_LEN_MAX 100 + +typedef enum { + BrowserItemTypeLoading, + BrowserItemTypeBack, + BrowserItemTypeFolder, + BrowserItemTypeFile, +} BrowserItemType; + +typedef struct { + string_t path; + BrowserItemType type; +} BrowserItem_t; + +static void BrowserItem_t_init(BrowserItem_t* obj) { + obj->type = BrowserItemTypeLoading; + string_init(obj->path); +} + +static void BrowserItem_t_init_set(BrowserItem_t* obj, const BrowserItem_t* src) { + obj->type = src->type; + string_init_set(obj->path, src->path); +} + +static void BrowserItem_t_set(BrowserItem_t* obj, const BrowserItem_t* src) { + obj->type = src->type; + string_set(obj->path, src->path); +} + +static void BrowserItem_t_clear(BrowserItem_t* obj) { + string_clear(obj->path); +} + +ARRAY_DEF( + items_array, + BrowserItem_t, + (INIT(API_2(BrowserItem_t_init)), + SET(API_6(BrowserItem_t_set)), + INIT_SET(API_6(BrowserItem_t_init_set)), + CLEAR(API_2(BrowserItem_t_clear)))) + +struct FileBrowser { + View* view; + BrowserWorker* worker; + const char* ext_filter; + bool skip_assets; + + FileBrowserCallback callback; + void* context; + + string_ptr result_path; +}; + +typedef struct { + items_array_t items; + + bool is_root; + bool folder_loading; + bool list_loading; + uint32_t item_cnt; + int32_t item_idx; + int32_t array_offset; + int32_t list_offset; + + const Icon* file_icon; + bool hide_ext; +} FileBrowserModel; + +static const Icon* BrowserItemIcons[] = { + [BrowserItemTypeLoading] = &I_loading_10px, + [BrowserItemTypeBack] = &I_back_10px, + [BrowserItemTypeFolder] = &I_dir_10px, + [BrowserItemTypeFile] = &I_unknown_10px, +}; + +static void file_browser_view_draw_callback(Canvas* canvas, void* _model); +static bool file_browser_view_input_callback(InputEvent* event, void* context); + +static void + browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root); +static void browser_list_load_cb(void* context, uint32_t list_load_offset); +static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last); +static void browser_long_load_cb(void* context); + +FileBrowser* file_browser_alloc(string_ptr result_path) { + furi_assert(result_path); + FileBrowser* browser = malloc(sizeof(FileBrowser)); + browser->view = view_alloc(); + view_allocate_model(browser->view, ViewModelTypeLocking, sizeof(FileBrowserModel)); + view_set_context(browser->view, browser); + view_set_draw_callback(browser->view, file_browser_view_draw_callback); + view_set_input_callback(browser->view, file_browser_view_input_callback); + + browser->result_path = result_path; + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_init(model->items); + return false; + }); + + return browser; +} + +void file_browser_free(FileBrowser* browser) { + furi_assert(browser); + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_clear(model->items); + return false; + }); + + view_free(browser->view); + free(browser); +} + +View* file_browser_get_view(FileBrowser* browser) { + furi_assert(browser); + return browser->view; +} + +void file_browser_configure( + FileBrowser* browser, + const char* extension, + bool skip_assets, + const Icon* file_icon, + bool hide_ext) { + furi_assert(browser); + + browser->ext_filter = extension; + browser->skip_assets = skip_assets; + + with_view_model( + browser->view, (FileBrowserModel * model) { + model->file_icon = file_icon; + model->hide_ext = hide_ext; + return false; + }); +} + +void file_browser_start(FileBrowser* browser, string_t path) { + furi_assert(browser); + browser->worker = file_browser_worker_alloc(path, browser->ext_filter, browser->skip_assets); + file_browser_worker_set_callback_context(browser->worker, browser); + file_browser_worker_set_folder_callback(browser->worker, browser_folder_open_cb); + file_browser_worker_set_list_callback(browser->worker, browser_list_load_cb); + file_browser_worker_set_item_callback(browser->worker, browser_list_item_cb); + file_browser_worker_set_long_load_callback(browser->worker, browser_long_load_cb); +} + +void file_browser_stop(FileBrowser* browser) { + furi_assert(browser); + file_browser_worker_free(browser->worker); + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_reset(model->items); + model->item_cnt = 0; + model->item_idx = 0; + model->array_offset = 0; + model->list_offset = 0; + return false; + }); +} + +void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context) { + browser->context = context; + browser->callback = callback; +} + +static bool browser_is_item_in_array(FileBrowserModel* model, uint32_t idx) { + size_t array_size = items_array_size(model->items); + + if((idx >= (uint32_t)model->array_offset + array_size) || + (idx < (uint32_t)model->array_offset)) { + return false; + } + return true; +} + +static bool browser_is_list_load_required(FileBrowserModel* model) { + size_t array_size = items_array_size(model->items); + uint32_t item_cnt = (model->is_root) ? model->item_cnt : model->item_cnt - 1; + + if((model->list_loading) || (array_size >= item_cnt)) { + return false; + } + + if((model->array_offset > 0) && + (model->item_idx < (model->array_offset + ITEM_LIST_LEN_MAX / 4))) { + return true; + } + + if(((model->array_offset + array_size) < item_cnt) && + (model->item_idx > (int32_t)(model->array_offset + array_size - ITEM_LIST_LEN_MAX / 4))) { + return true; + } + + return false; +} + +static void browser_update_offset(FileBrowser* browser) { + furi_assert(browser); + + with_view_model( + browser->view, (FileBrowserModel * model) { + uint16_t bounds = model->item_cnt > (LIST_ITEMS - 1) ? 2 : model->item_cnt; + + if((model->item_cnt > (LIST_ITEMS - 1)) && + (model->item_idx >= ((int32_t)model->item_cnt - 1))) { + model->list_offset = model->item_idx - (LIST_ITEMS - 1); + } else if(model->list_offset < model->item_idx - bounds) { + model->list_offset = CLAMP( + model->item_idx - (int32_t)(LIST_ITEMS - 2), + (int32_t)model->item_cnt - bounds, + 0); + } else if(model->list_offset > model->item_idx - bounds) { + model->list_offset = + CLAMP(model->item_idx - 1, (int32_t)model->item_cnt - bounds, 0); + } + + return false; + }); +} + +static void + browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root) { + furi_assert(context); + FileBrowser* browser = (FileBrowser*)context; + + int32_t load_offset = 0; + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_reset(model->items); + if(is_root) { + model->item_cnt = item_cnt; + model->item_idx = (file_idx > 0) ? file_idx : 0; + load_offset = + CLAMP(model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); + } else { + model->item_cnt = item_cnt + 1; + model->item_idx = file_idx + 1; + load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 2 - 1, (int32_t)model->item_cnt - 1, 0); + } + model->array_offset = 0; + model->list_offset = 0; + model->is_root = is_root; + model->list_loading = true; + model->folder_loading = false; + return true; + }); + browser_update_offset(browser); + + file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); +} + +static void browser_list_load_cb(void* context, uint32_t list_load_offset) { + furi_assert(context); + FileBrowser* browser = (FileBrowser*)context; + + BrowserItem_t back_item; + BrowserItem_t_init(&back_item); + back_item.type = BrowserItemTypeBack; + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_reset(model->items); + model->array_offset = list_load_offset; + if(!model->is_root) { + if(list_load_offset == 0) { + items_array_push_back(model->items, back_item); + } else { + model->array_offset += 1; + } + } + return false; + }); + + BrowserItem_t_clear(&back_item); +} + +static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last) { + furi_assert(context); + FileBrowser* browser = (FileBrowser*)context; + + BrowserItem_t item; + + if(!is_last) { + BrowserItem_t_init(&item); + string_set(item.path, item_path); + item.type = (is_folder) ? BrowserItemTypeFolder : BrowserItemTypeFile; + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_push_back(model->items, item); + return false; + }); + BrowserItem_t_clear(&item); + } else { + with_view_model( + browser->view, (FileBrowserModel * model) { + model->list_loading = false; + return true; + }); + } +} + +static void browser_long_load_cb(void* context) { + furi_assert(context); + FileBrowser* browser = (FileBrowser*)context; + + with_view_model( + browser->view, (FileBrowserModel * model) { + model->folder_loading = true; + return true; + }); +} + +static void browser_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box( + canvas, 0, Y_OFFSET + idx * FRAME_HEIGHT, (scrollbar ? 122 : 127), FRAME_HEIGHT); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_dot(canvas, 0, Y_OFFSET + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 1, Y_OFFSET + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 0, (Y_OFFSET + idx * FRAME_HEIGHT) + 1); + + canvas_draw_dot(canvas, 0, (Y_OFFSET + idx * FRAME_HEIGHT) + (FRAME_HEIGHT - 1)); + canvas_draw_dot(canvas, scrollbar ? 121 : 126, Y_OFFSET + idx * FRAME_HEIGHT); + canvas_draw_dot( + canvas, scrollbar ? 121 : 126, (Y_OFFSET + idx * FRAME_HEIGHT) + (FRAME_HEIGHT - 1)); +} + +static void browser_draw_loading(Canvas* canvas, FileBrowserModel* model) { + uint8_t width = 49; + uint8_t height = 47; + uint8_t x = 128 / 2 - width / 2; + uint8_t y = 64 / 2 - height / 2; + + UNUSED(model); + + elements_bold_rounded_frame(canvas, x, y, width, height); + + canvas_set_font(canvas, FontSecondary); + elements_multiline_text(canvas, x + 7, y + 13, "Loading..."); + + canvas_draw_icon(canvas, x + 13, y + 19, &A_Loading_24); +} + +static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { + uint32_t array_size = items_array_size(model->items); + bool show_scrollbar = model->item_cnt > LIST_ITEMS; + + string_t filename; + string_init(filename); + + for(uint32_t i = 0; i < MIN(model->item_cnt, LIST_ITEMS); i++) { + int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->item_cnt, 0u); + + BrowserItemType item_type = BrowserItemTypeLoading; + + if(browser_is_item_in_array(model, idx)) { + BrowserItem_t* item = items_array_get( + model->items, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0)); + item_type = item->type; + path_extract_filename( + item->path, filename, (model->hide_ext) && (item_type == BrowserItemTypeFile)); + } else { + string_set_str(filename, "---"); + } + + if(item_type == BrowserItemTypeBack) { + string_set_str(filename, ". ."); + } + + elements_string_fit_width( + canvas, filename, (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX)); + + if(model->item_idx == idx) { + browser_draw_frame(canvas, i, show_scrollbar); + } else { + canvas_set_color(canvas, ColorBlack); + } + + if((item_type == BrowserItemTypeFile) && (model->file_icon)) { + canvas_draw_icon(canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, model->file_icon); + } else if(BrowserItemIcons[item_type] != NULL) { + canvas_draw_icon( + canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, BrowserItemIcons[item_type]); + } + canvas_draw_str(canvas, 15, Y_OFFSET + 9 + i * FRAME_HEIGHT, string_get_cstr(filename)); + } + + if(show_scrollbar) { + elements_scrollbar_pos( + canvas, + 126, + Y_OFFSET, + canvas_height(canvas) - Y_OFFSET, + model->item_idx, + model->item_cnt); + } + + string_clear(filename); +} + +static void file_browser_view_draw_callback(Canvas* canvas, void* _model) { + FileBrowserModel* model = _model; + + if(model->folder_loading) { + browser_draw_loading(canvas, model); + } else { + browser_draw_list(canvas, model); + } +} + +static bool file_browser_view_input_callback(InputEvent* event, void* context) { + FileBrowser* browser = context; + furi_assert(browser); + bool consumed = false; + bool is_loading = false; + + with_view_model( + browser->view, (FileBrowserModel * model) { + is_loading = model->folder_loading; + return false; + }); + + if(is_loading) { + return false; + } else if(event->key == InputKeyUp || event->key == InputKeyDown) { + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + with_view_model( + browser->view, (FileBrowserModel * model) { + if(event->key == InputKeyUp) { + model->item_idx = + ((model->item_idx - 1) + model->item_cnt) % model->item_cnt; + if(browser_is_list_load_required(model)) { + model->list_loading = true; + int32_t load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 4 * 3, + (int32_t)model->item_cnt - ITEM_LIST_LEN_MAX, + 0); + file_browser_worker_load( + browser->worker, load_offset, ITEM_LIST_LEN_MAX); + } + } else if(event->key == InputKeyDown) { + model->item_idx = (model->item_idx + 1) % model->item_cnt; + if(browser_is_list_load_required(model)) { + model->list_loading = true; + int32_t load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 4 * 1, + (int32_t)model->item_cnt - ITEM_LIST_LEN_MAX, + 0); + file_browser_worker_load( + browser->worker, load_offset, ITEM_LIST_LEN_MAX); + } + } + return true; + }); + browser_update_offset(browser); + consumed = true; + } + } else if(event->key == InputKeyOk) { + if(event->type == InputTypeShort) { + BrowserItem_t* selected_item = NULL; + int32_t select_index = 0; + with_view_model( + browser->view, (FileBrowserModel * model) { + if(browser_is_item_in_array(model, model->item_idx)) { + selected_item = + items_array_get(model->items, model->item_idx - model->array_offset); + select_index = model->item_idx; + if((!model->is_root) && (select_index > 0)) { + select_index -= 1; + } + } + return false; + }); + + if(selected_item) { + if(selected_item->type == BrowserItemTypeBack) { + file_browser_worker_folder_exit(browser->worker); + } else if(selected_item->type == BrowserItemTypeFolder) { + file_browser_worker_folder_enter( + browser->worker, selected_item->path, select_index); + } else if(selected_item->type == BrowserItemTypeFile) { + string_set(browser->result_path, selected_item->path); + if(browser->callback) { + browser->callback(browser->context); + } + } + } + consumed = true; + } + } else if(event->key == InputKeyLeft) { + if(event->type == InputTypeShort) { + bool is_root = false; + with_view_model( + browser->view, (FileBrowserModel * model) { + is_root = model->is_root; + return false; + }); + if(!is_root) { + file_browser_worker_folder_exit(browser->worker); + } + consumed = true; + } + } + + return consumed; +} diff --git a/applications/gui/modules/file_browser.h b/applications/gui/modules/file_browser.h new file mode 100644 index 00000000..ebc64509 --- /dev/null +++ b/applications/gui/modules/file_browser.h @@ -0,0 +1,39 @@ +/** + * @file file_browser.h + * GUI: FileBrowser view module API + */ + +#pragma once + +#include "m-string.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FileBrowser FileBrowser; +typedef void (*FileBrowserCallback)(void* context); + +FileBrowser* file_browser_alloc(string_ptr result_path); + +void file_browser_free(FileBrowser* browser); + +View* file_browser_get_view(FileBrowser* browser); + +void file_browser_configure( + FileBrowser* browser, + const char* extension, + bool skip_assets, + const Icon* file_icon, + bool hide_ext); + +void file_browser_start(FileBrowser* browser, string_t path); + +void file_browser_stop(FileBrowser* browser); + +void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context); + +#ifdef __cplusplus +} +#endif diff --git a/applications/gui/modules/file_browser_worker.c b/applications/gui/modules/file_browser_worker.c new file mode 100644 index 00000000..93baba00 --- /dev/null +++ b/applications/gui/modules/file_browser_worker.c @@ -0,0 +1,408 @@ +#include "file_browser_worker.h" +#include "furi/check.h" +#include "furi/common_defines.h" +#include "m-string.h" +#include "storage/filesystem_api_defines.h" +#include +#include +#include +#include +#include +#include "toolbox/path.h" + +#define TAG "BrowserWorker" + +#define ASSETS_DIR "assets" +#define BROWSER_ROOT "/any" +#define FILE_NAME_LEN_MAX 256 +#define LONG_LOAD_THRESHOLD 100 + +typedef enum { + WorkerEvtStop = (1 << 0), + WorkerEvtLoad = (1 << 1), + WorkerEvtFolderEnter = (1 << 2), + WorkerEvtFolderExit = (1 << 3), +} WorkerEvtFlags; + +#define WORKER_FLAGS_ALL \ + (WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit) + +ARRAY_DEF(idx_last_array, int32_t) + +struct BrowserWorker { + FuriThread* thread; + + string_t filter_extension; + string_t path_next; + int32_t item_sel_idx; + uint32_t load_offset; + uint32_t load_count; + bool skip_assets; + idx_last_array_t idx_last; + + void* cb_ctx; + BrowserWorkerFolderOpenCallback folder_cb; + BrowserWorkerListLoadCallback list_load_cb; + BrowserWorkerListItemCallback list_item_cb; + BrowserWorkerLongLoadCallback long_load_cb; +}; + +static bool browser_path_is_file(string_t path) { + bool state = false; + FileInfo file_info; + Storage* storage = furi_record_open("storage"); + if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { + if((file_info.flags & FSF_DIRECTORY) == 0) { + state = true; + } + } + furi_record_close("storage"); + return state; +} + +static bool browser_path_trim(string_t path) { + bool is_root = false; + size_t filename_start = string_search_rchar(path, '/'); + string_left(path, filename_start); + if((string_empty_p(path)) || (filename_start == STRING_FAILURE)) { + string_set_str(path, BROWSER_ROOT); + is_root = true; + } + return is_root; +} + +static bool browser_filter_by_name(BrowserWorker* browser, string_t name, bool is_folder) { + if(is_folder) { + // Skip assets folders (if enabled) + if(browser->skip_assets) { + return ((string_cmp_str(name, ASSETS_DIR) == 0) ? (false) : (true)); + } else { + return true; + } + } else { + // Filter files by extension + if((string_empty_p(browser->filter_extension)) || + (string_cmp_str(browser->filter_extension, "*") == 0)) { + return true; + } + if(string_end_with_string_p(name, browser->filter_extension)) { + return true; + } + } + return false; +} + +static bool browser_folder_check_and_switch(string_t path) { + FileInfo file_info; + Storage* storage = furi_record_open("storage"); + bool is_root = false; + while(1) { + // Check if folder is existing and navigate back if not + if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { + if(file_info.flags & FSF_DIRECTORY) { + break; + } + } + if(is_root) { + break; + } + is_root = browser_path_trim(path); + } + furi_record_close("storage"); + return is_root; +} + +static bool browser_folder_init( + BrowserWorker* browser, + string_t path, + string_t filename, + uint32_t* item_cnt, + int32_t* file_idx) { + bool state = false; + FileInfo file_info; + uint32_t total_files_cnt = 0; + + Storage* storage = furi_record_open("storage"); + File* directory = storage_file_alloc(storage); + + char name_temp[FILE_NAME_LEN_MAX]; + string_t name_str; + string_init(name_str); + + *item_cnt = 0; + *file_idx = -1; + + if(storage_dir_open(directory, string_get_cstr(path))) { + state = true; + while(1) { + if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { + break; + } + if((storage_file_get_error(directory) == FSE_OK) && (name_temp[0] != '\0')) { + total_files_cnt++; + string_set_str(name_str, name_temp); + if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + if(!string_empty_p(filename)) { + if(string_cmp(name_str, filename) == 0) { + *file_idx = *item_cnt; + } + } + (*item_cnt)++; + } + if(total_files_cnt == LONG_LOAD_THRESHOLD) { + // There are too many files in folder and counting them will take some time - send callback to app + if(browser->long_load_cb) { + browser->long_load_cb(browser->cb_ctx); + } + } + } + } + } + + string_clear(name_str); + + storage_dir_close(directory); + storage_file_free(directory); + + furi_record_close("storage"); + + return state; +} + +static bool + browser_folder_load(BrowserWorker* browser, string_t path, uint32_t offset, uint32_t count) { + FileInfo file_info; + + Storage* storage = furi_record_open("storage"); + File* directory = storage_file_alloc(storage); + + char name_temp[FILE_NAME_LEN_MAX]; + string_t name_str; + string_init(name_str); + + uint32_t items_cnt = 0; + + do { + if(!storage_dir_open(directory, string_get_cstr(path))) { + break; + } + + items_cnt = 0; + while(items_cnt < offset) { + if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { + break; + } + if(storage_file_get_error(directory) == FSE_OK) { + string_set_str(name_str, name_temp); + if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + items_cnt++; + } + } else { + break; + } + } + if(items_cnt != offset) { + break; + } + + if(browser->list_load_cb) { + browser->list_load_cb(browser->cb_ctx, offset); + } + + items_cnt = 0; + while(items_cnt < count) { + if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { + break; + } + if(storage_file_get_error(directory) == FSE_OK) { + string_set_str(name_str, name_temp); + if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + string_printf(name_str, "%s/%s", string_get_cstr(path), name_temp); + if(browser->list_item_cb) { + browser->list_item_cb( + browser->cb_ctx, name_str, (file_info.flags & FSF_DIRECTORY), false); + } + items_cnt++; + } + } else { + break; + } + } + if(browser->list_item_cb) { + browser->list_item_cb(browser->cb_ctx, NULL, false, true); + } + } while(0); + + string_clear(name_str); + + storage_dir_close(directory); + storage_file_free(directory); + + furi_record_close("storage"); + + return (items_cnt == count); +} + +static int32_t browser_worker(void* context) { + BrowserWorker* browser = (BrowserWorker*)context; + furi_assert(browser); + FURI_LOG_D(TAG, "Start"); + + uint32_t items_cnt = 0; + string_t path; + string_init_set_str(path, BROWSER_ROOT); + browser->item_sel_idx = -1; + + // If start path is a path to the file - try finding index of this file in a folder + string_t filename; + string_init(filename); + if(browser_path_is_file(browser->path_next)) { + path_extract_filename(browser->path_next, filename, false); + } + + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); + + while(1) { + uint32_t flags = osThreadFlagsWait(WORKER_FLAGS_ALL, osFlagsWaitAny, osWaitForever); + furi_assert((flags & osFlagsError) == 0); + + if(flags & WorkerEvtFolderEnter) { + string_set(path, browser->path_next); + bool is_root = browser_folder_check_and_switch(path); + + // Push previous selected item index to history array + idx_last_array_push_back(browser->idx_last, browser->item_sel_idx); + + int32_t file_idx = 0; + browser_folder_init(browser, path, filename, &items_cnt, &file_idx); + FURI_LOG_D( + TAG, + "Enter folder: %s items: %u idx: %d", + string_get_cstr(path), + items_cnt, + file_idx); + if(browser->folder_cb) { + browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root); + } + string_reset(filename); + } + + if(flags & WorkerEvtFolderExit) { + browser_path_trim(path); + bool is_root = browser_folder_check_and_switch(path); + + int32_t file_idx = 0; + browser_folder_init(browser, path, filename, &items_cnt, &file_idx); + if(idx_last_array_size(browser->idx_last) > 0) { + // Pop previous selected item index from history array + idx_last_array_pop_back(&file_idx, browser->idx_last); + } + FURI_LOG_D( + TAG, "Exit to: %s items: %u idx: %d", string_get_cstr(path), items_cnt, file_idx); + if(browser->folder_cb) { + browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root); + } + } + + if(flags & WorkerEvtLoad) { + FURI_LOG_D(TAG, "Load offset: %u cnt: %u", browser->load_offset, browser->load_count); + browser_folder_load(browser, path, browser->load_offset, browser->load_count); + } + + if(flags & WorkerEvtStop) { + break; + } + } + + string_clear(filename); + string_clear(path); + + FURI_LOG_D(TAG, "End"); + return 0; +} + +BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets) { + BrowserWorker* browser = malloc(sizeof(BrowserWorker)); + + idx_last_array_init(browser->idx_last); + + string_init_set_str(browser->filter_extension, filter_ext); + browser->skip_assets = skip_assets; + string_init_set(browser->path_next, path); + + browser->thread = furi_thread_alloc(); + furi_thread_set_name(browser->thread, "BrowserWorker"); + furi_thread_set_stack_size(browser->thread, 2048); + furi_thread_set_context(browser->thread, browser); + furi_thread_set_callback(browser->thread, browser_worker); + furi_thread_start(browser->thread); + + return browser; +} + +void file_browser_worker_free(BrowserWorker* browser) { + furi_assert(browser); + + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtStop); + furi_thread_join(browser->thread); + furi_thread_free(browser->thread); + + string_clear(browser->filter_extension); + string_clear(browser->path_next); + + idx_last_array_clear(browser->idx_last); + + free(browser); +} + +void file_browser_worker_set_callback_context(BrowserWorker* browser, void* context) { + furi_assert(browser); + browser->cb_ctx = context; +} + +void file_browser_worker_set_folder_callback( + BrowserWorker* browser, + BrowserWorkerFolderOpenCallback cb) { + furi_assert(browser); + browser->folder_cb = cb; +} + +void file_browser_worker_set_list_callback( + BrowserWorker* browser, + BrowserWorkerListLoadCallback cb) { + furi_assert(browser); + browser->list_load_cb = cb; +} + +void file_browser_worker_set_item_callback( + BrowserWorker* browser, + BrowserWorkerListItemCallback cb) { + furi_assert(browser); + browser->list_item_cb = cb; +} + +void file_browser_worker_set_long_load_callback( + BrowserWorker* browser, + BrowserWorkerLongLoadCallback cb) { + furi_assert(browser); + browser->long_load_cb = cb; +} + +void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx) { + furi_assert(browser); + string_set(browser->path_next, path); + browser->item_sel_idx = item_idx; + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); +} + +void file_browser_worker_folder_exit(BrowserWorker* browser) { + furi_assert(browser); + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderExit); +} + +void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count) { + furi_assert(browser); + browser->load_offset = offset; + browser->load_count = count; + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtLoad); +} diff --git a/applications/gui/modules/file_browser_worker.h b/applications/gui/modules/file_browser_worker.h new file mode 100644 index 00000000..b0d360a3 --- /dev/null +++ b/applications/gui/modules/file_browser_worker.h @@ -0,0 +1,55 @@ +#pragma once + +#include "m-string.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BrowserWorker BrowserWorker; +typedef void (*BrowserWorkerFolderOpenCallback)( + void* context, + uint32_t item_cnt, + int32_t file_idx, + bool is_root); +typedef void (*BrowserWorkerListLoadCallback)(void* context, uint32_t list_load_offset); +typedef void (*BrowserWorkerListItemCallback)( + void* context, + string_t item_path, + bool is_folder, + bool is_last); +typedef void (*BrowserWorkerLongLoadCallback)(void* context); + +BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets); + +void file_browser_worker_free(BrowserWorker* browser); + +void file_browser_worker_set_callback_context(BrowserWorker* browser, void* context); + +void file_browser_worker_set_folder_callback( + BrowserWorker* browser, + BrowserWorkerFolderOpenCallback cb); + +void file_browser_worker_set_list_callback( + BrowserWorker* browser, + BrowserWorkerListLoadCallback cb); + +void file_browser_worker_set_item_callback( + BrowserWorker* browser, + BrowserWorkerListItemCallback cb); + +void file_browser_worker_set_long_load_callback( + BrowserWorker* browser, + BrowserWorkerLongLoadCallback cb); + +void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx); + +void file_browser_worker_folder_exit(BrowserWorker* browser); + +void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count); + +#ifdef __cplusplus +} +#endif diff --git a/applications/gui/modules/file_select.c b/applications/gui/modules/file_select.c deleted file mode 100644 index 14541196..00000000 --- a/applications/gui/modules/file_select.c +++ /dev/null @@ -1,475 +0,0 @@ -#include "file_select.h" -#include -#include -#include - -#define FILENAME_COUNT 4 - -struct FileSelect { - // public - View* view; - Storage* fs_api; - const char* path; - const char* extension; - - bool init_completed; - - FileSelectCallback callback; - void* context; - - char* buffer; - uint8_t buffer_size; -}; - -typedef struct { - string_t filename[FILENAME_COUNT]; - uint8_t position; - - uint16_t first_file_index; - uint16_t file_count; - -} FileSelectModel; - -bool file_select_fill_strings(FileSelect* file_select); -bool file_select_fill_count(FileSelect* file_select); -static bool file_select_init_inner(FileSelect* file_select); - -static void file_select_draw_callback(Canvas* canvas, void* _model) { - FileSelectModel* model = _model; - - string_t string_buff; - const uint8_t item_height = 16; - const uint8_t item_width = 123; - const uint8_t text_max_width = 115; - - canvas_clear(canvas); - canvas_set_font(canvas, FontSecondary); - - if(model->file_count) { - for(uint8_t i = 0; i < MIN(FILENAME_COUNT, model->file_count); i++) { - if(i == model->position) { - canvas_set_color(canvas, ColorBlack); - canvas_draw_box(canvas, 0, (i * item_height) + 1, item_width, item_height - 2); - - canvas_set_color(canvas, ColorWhite); - canvas_draw_dot(canvas, 0, (i * item_height) + 1); - canvas_draw_dot(canvas, 0, (i * item_height) + item_height - 2); - canvas_draw_dot(canvas, item_width - 1, (i * item_height) + 1); - canvas_draw_dot(canvas, item_width - 1, (i * item_height) + item_height - 2); - } else { - canvas_set_color(canvas, ColorBlack); - } - - string_init_set(string_buff, model->filename[i]); - elements_string_fit_width(canvas, string_buff, text_max_width); - canvas_draw_str( - canvas, 6, (i * item_height) + item_height - 4, string_get_cstr(string_buff)); - - string_clear(string_buff); - } - } else { - canvas_draw_str(canvas, 6, item_height, "Empty folder"); - } - elements_scrollbar(canvas, model->first_file_index + model->position, model->file_count); -} - -static bool file_select_input_callback(InputEvent* event, void* context) { - FileSelect* file_select = (FileSelect*)context; - bool consumed = false; - - if((event->type == InputTypeShort) | (event->type == InputTypeRepeat)) { - if(!file_select->init_completed) { - if(!file_select_init_inner(file_select)) { - file_select->callback(false, file_select->context); - } - } else if(event->key == InputKeyUp) { - with_view_model( - file_select->view, (FileSelectModel * model) { - if(model->position == 0) { - if(model->first_file_index == 0) { - // wrap - int16_t max_first_file_index = model->file_count - FILENAME_COUNT; - model->position = MIN(FILENAME_COUNT - 1, model->file_count - 1); - model->first_file_index = - max_first_file_index < 0 ? 0 : max_first_file_index; - } else { - model->first_file_index--; - } - } else if(model->position == 1) { - if(model->first_file_index == 0) { - model->position--; - } else { - model->first_file_index--; - } - } else { - model->position--; - } - return true; - }); - consumed = true; - if(!file_select_fill_strings(file_select)) { - file_select->callback(false, file_select->context); - } - } else if(event->key == InputKeyDown) { - with_view_model( - file_select->view, (FileSelectModel * model) { - uint16_t max_first_file_index = model->file_count > FILENAME_COUNT ? - model->file_count - FILENAME_COUNT : - 0; - - if(model->position >= MIN(FILENAME_COUNT - 1, model->file_count - 1)) { - if(model->first_file_index >= max_first_file_index) { - // wrap - model->position = 0; - model->first_file_index = 0; - } else { - model->first_file_index++; - } - } else if(model->position >= (FILENAME_COUNT - 2)) { - if(model->first_file_index >= max_first_file_index) { - model->position++; - } else { - model->first_file_index++; - } - } else { - model->position++; - } - return true; - }); - consumed = true; - if(!file_select_fill_strings(file_select)) { - file_select->callback(false, file_select->context); - } - } else if(event->key == InputKeyOk) { - if(file_select->callback != NULL) { - size_t files = 0; - if(file_select->buffer) { - with_view_model( - file_select->view, (FileSelectModel * model) { - files = model->file_count; - strlcpy( - file_select->buffer, - string_get_cstr(model->filename[model->position]), - file_select->buffer_size); - - return false; - }); - }; - if(files > 0) { - file_select->callback(true, file_select->context); - } - } - consumed = true; - } - } - - return consumed; -} - -static bool file_select_init_inner(FileSelect* file_select) { - bool result = false; - if(file_select->path && file_select->extension && file_select->fs_api) { - if(file_select_fill_count(file_select)) { - if(file_select_fill_strings(file_select)) { - file_select->init_completed = true; - result = true; - } - } - } - - return result; -} - -FileSelect* file_select_alloc() { - FileSelect* file_select = malloc(sizeof(FileSelect)); - file_select->view = view_alloc(); - file_select->fs_api = furi_record_open("storage"); - - view_set_context(file_select->view, file_select); - view_allocate_model(file_select->view, ViewModelTypeLockFree, sizeof(FileSelectModel)); - view_set_draw_callback(file_select->view, file_select_draw_callback); - view_set_input_callback(file_select->view, file_select_input_callback); - - with_view_model( - file_select->view, (FileSelectModel * model) { - for(uint8_t i = 0; i < FILENAME_COUNT; i++) { - string_init(model->filename[i]); - } - - model->first_file_index = 0; - model->file_count = 0; - return false; - }); - - return file_select; -} - -void file_select_free(FileSelect* file_select) { - furi_assert(file_select); - with_view_model( - file_select->view, (FileSelectModel * model) { - for(uint8_t i = 0; i < FILENAME_COUNT; i++) { - string_clear(model->filename[i]); - } - return false; - }); - view_free(file_select->view); - free(file_select); - furi_record_close("storage"); -} - -View* file_select_get_view(FileSelect* file_select) { - furi_assert(file_select); - return file_select->view; -} - -void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context) { - file_select->context = context; - file_select->callback = callback; -} - -void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension) { - furi_assert(file_select); - file_select->path = path; - file_select->extension = extension; -} - -void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size) { - file_select->buffer = buffer; - file_select->buffer_size = buffer_size; - - if(file_select->buffer) { - strlcpy(file_select->buffer, "", file_select->buffer_size); - } -} - -bool file_select_init(FileSelect* file_select) { - if(!file_select_init_inner(file_select)) { - file_select->callback(false, file_select->context); - return false; - } else { - return true; - } -} - -static bool filter_file(FileSelect* file_select, FileInfo* file_info, char* name) { - bool result = false; - - if(!(file_info->flags & FSF_DIRECTORY)) { - if(strcmp(file_select->extension, "*") == 0) { - result = true; - } else if(strstr(name, file_select->extension) != NULL) { - result = true; - } - } - - return result; -} - -bool file_select_fill_strings(FileSelect* file_select) { - furi_assert(file_select); - furi_assert(file_select->fs_api); - furi_assert(file_select->path); - furi_assert(file_select->extension); - - FileInfo file_info; - File* directory = storage_file_alloc(file_select->fs_api); - - uint8_t string_counter = 0; - uint16_t file_counter = 0; - const uint8_t name_length = 100; - char* name = malloc(name_length); - uint16_t first_file_index = 0; - - with_view_model( - file_select->view, (FileSelectModel * model) { - first_file_index = model->first_file_index; - return false; - }); - - if(!storage_dir_open(directory, file_select->path)) { - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return true; - } - - while(1) { - if(!storage_dir_read(directory, &file_info, name, name_length)) { - break; - } - - if(storage_file_get_error(directory) == FSE_OK) { - if(filter_file(file_select, &file_info, name)) { - if(file_counter >= first_file_index) { - with_view_model( - file_select->view, (FileSelectModel * model) { - string_set_str(model->filename[string_counter], name); - - if(strcmp(file_select->extension, "*") != 0) { - string_replace_all_str( - model->filename[string_counter], file_select->extension, ""); - } - - return true; - }); - string_counter++; - - if(string_counter >= FILENAME_COUNT) { - break; - } - } - file_counter++; - } - } else { - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return false; - } - } - - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return true; -} - -bool file_select_fill_count(FileSelect* file_select) { - furi_assert(file_select); - furi_assert(file_select->fs_api); - furi_assert(file_select->path); - furi_assert(file_select->extension); - - FileInfo file_info; - File* directory = storage_file_alloc(file_select->fs_api); - - uint16_t file_counter = 0; - const uint8_t name_length = 100; - char* name = malloc(name_length); - - if(!storage_dir_open(directory, file_select->path)) { - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return true; - } - - while(1) { - if(!storage_dir_read(directory, &file_info, name, name_length)) { - break; - } - - if(storage_file_get_error(directory) == FSE_OK) { - if(filter_file(file_select, &file_info, name)) { - file_counter++; - } - } else { - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return false; - } - } - - with_view_model( - file_select->view, (FileSelectModel * model) { - model->file_count = file_counter; - return false; - }); - - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return true; -} - -void file_select_set_selected_file_internal(FileSelect* file_select, const char* filename) { - furi_assert(file_select); - furi_assert(filename); - furi_assert(file_select->fs_api); - furi_assert(file_select->path); - furi_assert(file_select->extension); - - if(strlen(filename) == 0) return; - - FileInfo file_info; - File* directory = storage_file_alloc(file_select->fs_api); - - const uint8_t name_length = 100; - char* name = malloc(name_length); - uint16_t file_position = 0; - bool file_found = false; - - string_t filename_str; - string_init_set_str(filename_str, filename); - if(strcmp(file_select->extension, "*") != 0) { - string_cat_str(filename_str, file_select->extension); - } - - if(!storage_dir_open(directory, file_select->path)) { - string_clear(filename_str); - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return; - } - - while(1) { - if(!storage_dir_read(directory, &file_info, name, name_length)) { - break; - } - - if(storage_file_get_error(directory) == FSE_OK) { - if(filter_file(file_select, &file_info, name)) { - if(strcmp(string_get_cstr(filename_str), name) == 0) { - file_found = true; - break; - } - - file_position++; - } - } else { - string_clear(filename_str); - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return; - } - } - - if(file_found) { - with_view_model( - file_select->view, (FileSelectModel * model) { - uint16_t max_first_file_index = - model->file_count > FILENAME_COUNT ? model->file_count - FILENAME_COUNT : 0; - - model->first_file_index = file_position; - - if(model->first_file_index > 0) { - model->first_file_index -= 1; - } - - if(model->first_file_index >= max_first_file_index) { - model->first_file_index = max_first_file_index; - } - - model->position = file_position - model->first_file_index; - - return true; - }); - } - - string_clear(filename_str); - storage_dir_close(directory); - storage_file_free(directory); - free(name); -} - -void file_select_set_selected_file(FileSelect* file_select, const char* filename) { - file_select_set_selected_file_internal(file_select, filename); - - if(!file_select_fill_strings(file_select)) { - file_select->callback(false, file_select->context); - } -} diff --git a/applications/gui/modules/file_select.h b/applications/gui/modules/file_select.h deleted file mode 100644 index ed3d5b60..00000000 --- a/applications/gui/modules/file_select.h +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @file file_select.h - * GUI: FileSelect view module API - */ - -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct FileSelect FileSelect; - -typedef void (*FileSelectCallback)(bool result, void* context); - -FileSelect* file_select_alloc(); - -void file_select_free(FileSelect* file_select); -View* file_select_get_view(FileSelect* file_select); - -void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context); -void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension); -void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size); -bool file_select_init(FileSelect* file_select); -void file_select_set_selected_file(FileSelect* file_select, const char* filename); - -#ifdef __cplusplus -} -#endif diff --git a/applications/gui/modules/validators.c b/applications/gui/modules/validators.c index 242dbe68..546423d0 100644 --- a/applications/gui/modules/validators.c +++ b/applications/gui/modules/validators.c @@ -3,7 +3,7 @@ #include "applications/storage/storage.h" struct ValidatorIsFile { - const char* app_path_folder; + char* app_path_folder; const char* app_extension; char* current_name; }; @@ -40,7 +40,7 @@ ValidatorIsFile* validator_is_file_alloc_init( const char* current_name) { ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile)); - instance->app_path_folder = app_path_folder; + instance->app_path_folder = strdup(app_path_folder); instance->app_extension = app_extension; instance->current_name = strdup(current_name); @@ -49,6 +49,7 @@ ValidatorIsFile* validator_is_file_alloc_init( void validator_is_file_free(ValidatorIsFile* instance) { furi_assert(instance); + free(instance->app_path_folder); free(instance->current_name); free(instance); } diff --git a/applications/ibutton/ibutton.c b/applications/ibutton/ibutton.c index a38f077f..b1b5bf62 100644 --- a/applications/ibutton/ibutton.c +++ b/applications/ibutton/ibutton.c @@ -1,7 +1,8 @@ #include "ibutton.h" +#include "assets_icons.h" #include "ibutton_i.h" #include "ibutton/scenes/ibutton_scene.h" - +#include "m-string.h" #include #include @@ -85,6 +86,8 @@ void ibutton_tick_event_callback(void* context) { iButton* ibutton_alloc() { iButton* ibutton = malloc(sizeof(iButton)); + string_init(ibutton->file_path); + ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton); ibutton->view_dispatcher = view_dispatcher_alloc(); @@ -176,49 +179,28 @@ void ibutton_free(iButton* ibutton) { ibutton_worker_free(ibutton->key_worker); ibutton_key_free(ibutton->key); + string_clear(ibutton->file_path); + free(ibutton); } bool ibutton_file_select(iButton* ibutton) { - bool success = dialog_file_select_show( + bool success = dialog_file_browser_show( ibutton->dialogs, - IBUTTON_APP_FOLDER, + ibutton->file_path, + ibutton->file_path, IBUTTON_APP_EXTENSION, - ibutton->file_name, - IBUTTON_FILE_NAME_SIZE, - ibutton_key_get_name_p(ibutton->key)); + true, + &I_ibutt_10px, + true); if(success) { - string_t key_str; - string_init_printf( - key_str, "%s/%s%s", IBUTTON_APP_FOLDER, ibutton->file_name, IBUTTON_APP_EXTENSION); - success = ibutton_load_key_data(ibutton, key_str); - - if(success) { - ibutton_key_set_name(ibutton->key, ibutton->file_name); - } - - string_clear(key_str); + success = ibutton_load_key_data(ibutton, ibutton->file_path); } return success; } -bool ibutton_load_key(iButton* ibutton, const char* key_name) { - string_t key_path; - string_init_set_str(key_path, key_name); - - const bool success = ibutton_load_key_data(ibutton, key_path); - - if(success) { - path_extract_filename_no_ext(key_name, key_path); - ibutton_key_set_name(ibutton->key, string_get_cstr(key_path)); - } - - string_clear(key_path); - return success; -} - bool ibutton_save_key(iButton* ibutton, const char* key_name) { // Create ibutton directory if necessary ibutton_make_app_folder(ibutton); @@ -226,27 +208,22 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { FlipperFormat* file = flipper_format_file_alloc(ibutton->storage); iButtonKey* key = ibutton->key; - string_t key_file_name; bool result = false; - string_init(key_file_name); do { // First remove key if it was saved (we rename the key) - if(!ibutton_delete_key(ibutton)) break; - - // Save the key - ibutton_key_set_name(key, key_name); + ibutton_delete_key(ibutton); // Set full file name, for new key - string_printf( - key_file_name, - "%s/%s%s", - IBUTTON_APP_FOLDER, - ibutton_key_get_name_p(key), - IBUTTON_APP_EXTENSION); + if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + size_t filename_start = string_search_rchar(ibutton->file_path, '/'); + string_left(ibutton->file_path, filename_start); + } + + string_cat_printf(ibutton->file_path, "/%s%s", key_name, IBUTTON_APP_EXTENSION); // Open file for write - if(!flipper_format_file_open_always(file, string_get_cstr(key_file_name))) break; + if(!flipper_format_file_open_always(file, string_get_cstr(ibutton->file_path))) break; // Write header if(!flipper_format_write_header_cstr(file, IBUTTON_APP_FILE_TYPE, 1)) break; @@ -271,8 +248,6 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { flipper_format_free(file); - string_clear(key_file_name); - if(!result) { dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file"); } @@ -281,17 +256,8 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { } bool ibutton_delete_key(iButton* ibutton) { - string_t file_name; bool result = false; - - string_init_printf( - file_name, - "%s/%s%s", - IBUTTON_APP_FOLDER, - ibutton_key_get_name_p(ibutton->key), - IBUTTON_APP_EXTENSION); - result = storage_simply_remove(ibutton->storage, string_get_cstr(file_name)); - string_clear(file_name); + result = storage_simply_remove(ibutton->storage, string_get_cstr(ibutton->file_path)); return result; } @@ -335,8 +301,17 @@ int32_t ibutton_app(void* p) { ibutton_make_app_folder(ibutton); - if(p && ibutton_load_key(ibutton, (const char*)p)) { - // TODO: Display an error if the key from p could not be loaded + bool key_loaded = false; + + if(p) { + string_set_str(ibutton->file_path, (const char*)p); + if(ibutton_load_key_data(ibutton, ibutton->file_path)) { + key_loaded = true; + // TODO: Display an error if the key from p could not be loaded + } + } + + if(key_loaded) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); } else { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart); diff --git a/applications/ibutton/ibutton_i.h b/applications/ibutton/ibutton_i.h index 36857fd6..e66712be 100644 --- a/applications/ibutton/ibutton_i.h +++ b/applications/ibutton/ibutton_i.h @@ -41,7 +41,7 @@ struct iButton { iButtonWorker* key_worker; iButtonKey* key; - char file_name[IBUTTON_FILE_NAME_SIZE]; + string_t file_path; char text_store[IBUTTON_TEXT_STORE_SIZE + 1]; Submenu* submenu; @@ -74,7 +74,6 @@ typedef enum { } iButtonNotificationMessage; bool ibutton_file_select(iButton* ibutton); -bool ibutton_load_key(iButton* ibutton, const char* key_name); bool ibutton_save_key(iButton* ibutton, const char* key_name); bool ibutton_delete_key(iButton* ibutton); void ibutton_text_store_set(iButton* ibutton, const char* text, ...); diff --git a/applications/ibutton/scenes/ibutton_scene_add_type.c b/applications/ibutton/scenes/ibutton_scene_add_type.c index db129295..273330e7 100644 --- a/applications/ibutton/scenes/ibutton_scene_add_type.c +++ b/applications/ibutton/scenes/ibutton_scene_add_type.c @@ -1,4 +1,5 @@ #include "../ibutton_i.h" +#include "m-string.h" enum SubmenuIndex { SubmenuIndexCyfral, @@ -44,7 +45,7 @@ bool ibutton_scene_add_type_on_event(void* context, SceneManagerEvent event) { furi_crash("Unknown key type"); } - ibutton_key_set_name(key, ""); + string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); ibutton_key_clear_data(key); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue); } diff --git a/applications/ibutton/scenes/ibutton_scene_delete_confirm.c b/applications/ibutton/scenes/ibutton_scene_delete_confirm.c index 73ea97cc..51f1f279 100644 --- a/applications/ibutton/scenes/ibutton_scene_delete_confirm.c +++ b/applications/ibutton/scenes/ibutton_scene_delete_confirm.c @@ -1,4 +1,5 @@ #include "../ibutton_i.h" +#include static void ibutton_scene_delete_confirm_widget_callback( GuiButtonType result, @@ -16,7 +17,11 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { iButtonKey* key = ibutton->key; const uint8_t* key_data = ibutton_key_get_data_p(key); - ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", ibutton_key_get_name_p(key)); + string_t key_name; + string_init(key_name); + path_extract_filename(ibutton->file_path, key_name, true); + + ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", string_get_cstr(key_name)); widget_add_text_box_element( widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, false); widget_add_button_element( @@ -62,6 +67,8 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { widget, 64, 33, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + + string_clear(key_name); } bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/ibutton/scenes/ibutton_scene_emulate.c b/applications/ibutton/scenes/ibutton_scene_emulate.c index 8ffe73b6..59022397 100644 --- a/applications/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/ibutton/scenes/ibutton_scene_emulate.c @@ -1,5 +1,6 @@ #include "../ibutton_i.h" #include +#include static void ibutton_scene_emulate_callback(void* context, bool emulated) { iButton* ibutton = context; @@ -15,14 +16,19 @@ void ibutton_scene_emulate_on_enter(void* context) { iButtonKey* key = ibutton->key; const uint8_t* key_data = ibutton_key_get_data_p(key); - const char* key_name = ibutton_key_get_name_p(key); + + string_t key_name; + string_init(key_name); + if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + path_extract_filename(ibutton->file_path, key_name, true); + } uint8_t line_count = 2; DOLPHIN_DEED(DolphinDeedIbuttonEmulate); // check that stored key has name - if(strcmp(key_name, "") != 0) { - ibutton_text_store_set(ibutton, "emulating\n%s", key_name); + if(!string_empty_p(key_name)) { + ibutton_text_store_set(ibutton, "emulating\n%s", string_get_cstr(key_name)); line_count = 2; } else { // if not, show key data @@ -77,6 +83,8 @@ void ibutton_scene_emulate_on_enter(void* context) { ibutton_worker_emulate_set_callback( ibutton->key_worker, ibutton_scene_emulate_callback, ibutton); ibutton_worker_emulate_start(ibutton->key_worker, key); + + string_clear(key_name); } bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/ibutton/scenes/ibutton_scene_info.c b/applications/ibutton/scenes/ibutton_scene_info.c index 5b0af1d8..bd364ada 100644 --- a/applications/ibutton/scenes/ibutton_scene_info.c +++ b/applications/ibutton/scenes/ibutton_scene_info.c @@ -1,4 +1,5 @@ #include "../ibutton_i.h" +#include void ibutton_scene_info_on_enter(void* context) { iButton* ibutton = context; @@ -7,7 +8,11 @@ void ibutton_scene_info_on_enter(void* context) { const uint8_t* key_data = ibutton_key_get_data_p(key); - ibutton_text_store_set(ibutton, "%s", ibutton_key_get_name_p(key)); + string_t key_name; + string_init(key_name); + path_extract_filename(ibutton->file_path, key_name, true); + + ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); widget_add_text_box_element( widget, 0, 0, 128, 28, AlignCenter, AlignCenter, ibutton->text_store, false); @@ -46,6 +51,8 @@ void ibutton_scene_info_on_enter(void* context) { widget, 64, 35, AlignCenter, AlignBottom, FontPrimary, ibutton->text_store); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + + string_clear(key_name); } bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/ibutton/scenes/ibutton_scene_read.c b/applications/ibutton/scenes/ibutton_scene_read.c index a25f27e6..0cc0a8df 100644 --- a/applications/ibutton/scenes/ibutton_scene_read.c +++ b/applications/ibutton/scenes/ibutton_scene_read.c @@ -18,7 +18,7 @@ void ibutton_scene_read_on_enter(void* context) { popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); - ibutton_key_set_name(key, ""); + string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton); ibutton_worker_read_start(worker, key); diff --git a/applications/ibutton/scenes/ibutton_scene_save_name.c b/applications/ibutton/scenes/ibutton_scene_save_name.c index b1baf6af..6caf5d2d 100644 --- a/applications/ibutton/scenes/ibutton_scene_save_name.c +++ b/applications/ibutton/scenes/ibutton_scene_save_name.c @@ -1,5 +1,7 @@ #include "../ibutton_i.h" +#include "m-string.h" #include +#include static void ibutton_scene_save_name_text_input_callback(void* context) { iButton* ibutton = context; @@ -10,13 +12,17 @@ void ibutton_scene_save_name_on_enter(void* context) { iButton* ibutton = context; TextInput* text_input = ibutton->text_input; - const char* key_name = ibutton_key_get_name_p(ibutton->key); - const bool key_name_is_empty = !strcmp(key_name, ""); + string_t key_name; + string_init(key_name); + if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + path_extract_filename(ibutton->file_path, key_name, true); + } + const bool key_name_is_empty = string_empty_p(key_name); if(key_name_is_empty) { set_random_name(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE); } else { - ibutton_text_store_set(ibutton, "%s", key_name); + ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); } text_input_set_header_text(text_input, "Name the key"); @@ -28,11 +34,19 @@ void ibutton_scene_save_name_on_enter(void* context) { IBUTTON_KEY_NAME_SIZE, key_name_is_empty); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(IBUTTON_APP_FOLDER, IBUTTON_APP_EXTENSION, key_name); + string_t folder_path; + string_init(folder_path); + + path_extract_dirname(string_get_cstr(ibutton->file_path), folder_path); + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + string_get_cstr(folder_path), IBUTTON_APP_EXTENSION, string_get_cstr(key_name)); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput); + + string_clear(key_name); + string_clear(folder_path); } bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/ibutton/scenes/ibutton_scene_start.c b/applications/ibutton/scenes/ibutton_scene_start.c index a3141f5a..cc8af983 100644 --- a/applications/ibutton/scenes/ibutton_scene_start.c +++ b/applications/ibutton/scenes/ibutton_scene_start.c @@ -36,6 +36,7 @@ bool ibutton_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexRead) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead); } else if(event.event == SubmenuIndexSaved) { + string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey); } else if(event.event == SubmenuIndexAdd) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddType); diff --git a/applications/ibutton/scenes/ibutton_scene_write.c b/applications/ibutton/scenes/ibutton_scene_write.c index 35e45d83..4ce19408 100644 --- a/applications/ibutton/scenes/ibutton_scene_write.c +++ b/applications/ibutton/scenes/ibutton_scene_write.c @@ -1,4 +1,6 @@ #include "../ibutton_i.h" +#include "m-string.h" +#include "toolbox/path.h" typedef enum { iButtonSceneWriteStateDefault, @@ -17,13 +19,18 @@ void ibutton_scene_write_on_enter(void* context) { iButtonWorker* worker = ibutton->key_worker; const uint8_t* key_data = ibutton_key_get_data_p(key); - const char* key_name = ibutton_key_get_name_p(key); + + string_t key_name; + string_init(key_name); + if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + path_extract_filename(ibutton->file_path, key_name, true); + } uint8_t line_count = 2; // check that stored key has name - if(strcmp(key_name, "") != 0) { - ibutton_text_store_set(ibutton, "writing\n%s", key_name); + if(!string_empty_p(key_name)) { + ibutton_text_store_set(ibutton, "writing\n%s", string_get_cstr(key_name)); line_count = 2; } else { // if not, show key data @@ -79,6 +86,8 @@ void ibutton_scene_write_on_enter(void* context) { ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); ibutton_worker_write_start(worker, key); + + string_clear(key_name); } bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/infrared/infrared_app.cpp b/applications/infrared/infrared_app.cpp index 5dc3b311..1ac859d1 100644 --- a/applications/infrared/infrared_app.cpp +++ b/applications/infrared/infrared_app.cpp @@ -1,4 +1,5 @@ #include "infrared_app.h" +#include "m-string.h" #include #include #include @@ -12,20 +13,18 @@ int32_t InfraredApp::run(void* args) { bool exit = false; if(args) { - std::string path = static_cast(args); - std::string remote_name(path, path.find_last_of('/') + 1, path.size()); - auto last_dot = remote_name.find_last_of('.'); - if(last_dot != std::string::npos) { - remote_name.erase(last_dot); - path.erase(path.find_last_of('/')); - bool result = remote_manager.load(path, remote_name); + string_t path; + string_init_set_str(path, (char*)args); + if(string_end_with_str_p(path, InfraredApp::infrared_extension)) { + bool result = remote_manager.load(path); if(result) { current_scene = InfraredApp::Scene::Remote; } else { - printf("Failed to load remote \'%s\'\r\n", remote_name.c_str()); + printf("Failed to load remote \'%s\'\r\n", string_get_cstr(path)); return -1; } } + string_clear(path); } scenes[current_scene]->on_enter(this); @@ -51,6 +50,7 @@ int32_t InfraredApp::run(void* args) { InfraredApp::InfraredApp() { furi_check(InfraredAppRemoteManager::max_button_name_length < get_text_store_size()); + string_init_set_str(file_path, InfraredApp::infrared_directory); notification = static_cast(furi_record_open("notification")); dialogs = static_cast(furi_record_open("dialogs")); infrared_worker = infrared_worker_alloc(); @@ -60,6 +60,7 @@ InfraredApp::~InfraredApp() { infrared_worker_free(infrared_worker); furi_record_close("notification"); furi_record_close("dialogs"); + string_clear(file_path); for(auto& [key, scene] : scenes) delete scene; } diff --git a/applications/infrared/infrared_app.h b/applications/infrared/infrared_app.h index e0db7c51..1cb8b661 100644 --- a/applications/infrared/infrared_app.h +++ b/applications/infrared/infrared_app.h @@ -251,6 +251,8 @@ public: /** Main class destructor, deinitializes all critical objects */ ~InfraredApp(); + string_t file_path; + /** Path to Infrared directory */ static constexpr const char* infrared_directory = "/any/infrared"; /** Infrared files extension (remote files and universal databases) */ diff --git a/applications/infrared/infrared_app_remote_manager.cpp b/applications/infrared/infrared_app_remote_manager.cpp index 4fd64553..faeccb39 100644 --- a/applications/infrared/infrared_app_remote_manager.cpp +++ b/applications/infrared/infrared_app_remote_manager.cpp @@ -1,3 +1,5 @@ +#include "m-string.h" +#include "storage/filesystem_api_defines.h" #include #include "infrared_app_remote_manager.h" #include "infrared/helpers/infrared_parser.h" @@ -11,44 +13,58 @@ #include #include #include "infrared_app.h" +#include static const char* default_remote_name = "remote"; -std::string InfraredAppRemoteManager::make_full_name( - const std::string& path, - const std::string& remote_name) const { - return std::string("") + path + "/" + remote_name + InfraredApp::infrared_extension; -} - -std::string InfraredAppRemoteManager::find_vacant_remote_name(const std::string& name) { - std::string result_name; +void InfraredAppRemoteManager::find_vacant_remote_name(string_t name, string_t path) { Storage* storage = static_cast(furi_record_open("storage")); - FS_Error error = storage_common_stat( - storage, make_full_name(InfraredApp::infrared_directory, name).c_str(), NULL); + string_t base_path; + string_init_set(base_path, path); - if(error == FSE_NOT_EXIST) { - result_name = name; - } else if(error != FSE_OK) { - result_name = std::string(); - } else { + if(string_end_with_str_p(base_path, InfraredApp::infrared_extension)) { + size_t filename_start = string_search_rchar(base_path, '/'); + string_left(base_path, filename_start); + } + + string_printf( + base_path, + "%s/%s%s", + string_get_cstr(path), + string_get_cstr(name), + InfraredApp::infrared_extension); + + FS_Error error = storage_common_stat(storage, string_get_cstr(base_path), NULL); + + if(error == FSE_OK) { /* if suggested name is occupied, try another one (name2, name3, etc) */ + size_t dot = string_search_rchar(base_path, '.'); + string_left(base_path, dot); + + string_t path_temp; + string_init(path_temp); + uint32_t i = 1; - std::string new_name; do { - new_name = make_full_name(InfraredApp::infrared_directory, name + std::to_string(++i)); - error = storage_common_stat(storage, new_name.c_str(), NULL); + string_printf( + path_temp, + "%s%u%s", + string_get_cstr(base_path), + ++i, + InfraredApp::infrared_extension); + error = storage_common_stat(storage, string_get_cstr(path_temp), NULL); } while(error == FSE_OK); + string_clear(path_temp); + if(error == FSE_NOT_EXIST) { - result_name = name + std::to_string(i); - } else { - result_name = std::string(); + string_cat_printf(name, "%u", i); } } + string_clear(base_path); furi_record_close("storage"); - return result_name; } bool InfraredAppRemoteManager::add_button(const char* button_name, const InfraredAppSignal& signal) { @@ -61,12 +77,23 @@ bool InfraredAppRemoteManager::add_remote_with_button( const InfraredAppSignal& signal) { furi_check(button_name != nullptr); - auto new_name = find_vacant_remote_name(default_remote_name); - if(new_name.empty()) { - return false; - } + string_t new_name; + string_init_set_str(new_name, default_remote_name); + + string_t new_path; + string_init_set_str(new_path, InfraredApp::infrared_directory); + + find_vacant_remote_name(new_name, new_path); + + string_cat_printf( + new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension); + + remote = std::make_unique(new_path); + remote->name = std::string(string_get_cstr(new_name)); + + string_clear(new_path); + string_clear(new_name); - remote = std::make_unique(InfraredApp::infrared_directory, new_name); return add_button(button_name, signal); } @@ -93,8 +120,7 @@ const InfraredAppSignal& InfraredAppRemoteManager::get_button_data(size_t index) bool InfraredAppRemoteManager::delete_remote() { Storage* storage = static_cast(furi_record_open("storage")); - FS_Error error = - storage_common_remove(storage, make_full_name(remote->path, remote->name).c_str()); + FS_Error error = storage_common_remove(storage, string_get_cstr(remote->path)); reset_remote(); furi_record_close("storage"); @@ -128,22 +154,33 @@ std::string InfraredAppRemoteManager::get_remote_name() { bool InfraredAppRemoteManager::rename_remote(const char* str) { furi_check(str != nullptr); furi_check(remote.get() != nullptr); + furi_check(!string_empty_p(remote->path)); if(!remote->name.compare(str)) { return true; } - auto new_name = find_vacant_remote_name(str); - if(new_name.empty()) { - return false; + string_t new_name; + string_init_set_str(new_name, str); + find_vacant_remote_name(new_name, remote->path); + + string_t new_path; + string_init_set(new_path, remote->path); + if(string_end_with_str_p(new_path, InfraredApp::infrared_extension)) { + size_t filename_start = string_search_rchar(new_path, '/'); + string_left(new_path, filename_start); } + string_cat_printf( + new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension); Storage* storage = static_cast(furi_record_open("storage")); - std::string old_filename = make_full_name(remote->path, remote->name); - std::string new_filename = make_full_name(remote->path, new_name); - FS_Error error = storage_common_rename(storage, old_filename.c_str(), new_filename.c_str()); - remote->name = new_name; + FS_Error error = + storage_common_rename(storage, string_get_cstr(remote->path), string_get_cstr(new_path)); + remote->name = std::string(string_get_cstr(new_name)); + + string_clear(new_name); + string_clear(new_path); furi_record_close("storage"); return (error == FSE_OK || error == FSE_EXIST); @@ -171,10 +208,8 @@ bool InfraredAppRemoteManager::store(void) { FlipperFormat* ff = flipper_format_file_alloc(storage); - FURI_LOG_I( - "RemoteManager", "store file: \'%s\'", make_full_name(remote->path, remote->name).c_str()); - result = - flipper_format_file_open_always(ff, make_full_name(remote->path, remote->name).c_str()); + FURI_LOG_I("RemoteManager", "store file: \'%s\'", string_get_cstr(remote->path)); + result = flipper_format_file_open_always(ff, string_get_cstr(remote->path)); if(result) { result = flipper_format_write_header_cstr(ff, "IR signals file", 1); } @@ -192,13 +227,13 @@ bool InfraredAppRemoteManager::store(void) { return result; } -bool InfraredAppRemoteManager::load(const std::string& path, const std::string& remote_name) { +bool InfraredAppRemoteManager::load(string_t path) { bool result = false; Storage* storage = static_cast(furi_record_open("storage")); FlipperFormat* ff = flipper_format_file_alloc(storage); - FURI_LOG_I("RemoteManager", "load file: \'%s\'", make_full_name(path, remote_name).c_str()); - result = flipper_format_file_open_existing(ff, make_full_name(path, remote_name).c_str()); + FURI_LOG_I("RemoteManager", "load file: \'%s\'", string_get_cstr(path)); + result = flipper_format_file_open_existing(ff, string_get_cstr(path)); if(result) { string_t header; string_init(header); @@ -210,7 +245,14 @@ bool InfraredAppRemoteManager::load(const std::string& path, const std::string& string_clear(header); } if(result) { - remote = std::make_unique(path, remote_name); + string_t new_name; + string_init(new_name); + + remote = std::make_unique(path); + path_extract_filename(path, new_name, true); + remote->name = std::string(string_get_cstr(new_name)); + + string_clear(new_name); InfraredAppSignal signal; std::string signal_name; while(infrared_parser_read_signal(ff, signal, signal_name)) { diff --git a/applications/infrared/infrared_app_remote_manager.h b/applications/infrared/infrared_app_remote_manager.h index 31557d54..b6f0b170 100644 --- a/applications/infrared/infrared_app_remote_manager.h +++ b/applications/infrared/infrared_app_remote_manager.h @@ -8,6 +8,7 @@ #include "infrared_app_signal.h" +#include "m-string.h" #include #include @@ -60,17 +61,19 @@ class InfraredAppRemote { /** Name of remote */ std::string name; /** Path to remote file */ - std::string path; + string_t path; public: /** Initialize new remote * * @param path - remote file path - * @param name - new remote name */ - InfraredAppRemote(const std::string& path, const std::string& name) - : name(name) - , path(path) { + InfraredAppRemote(string_t file_path) { + string_init_set(path, file_path); + } + + ~InfraredAppRemote() { + string_clear(path); } }; @@ -78,12 +81,6 @@ public: class InfraredAppRemoteManager { /** Remote instance. There can be 1 remote loaded at a time. */ std::unique_ptr remote; - /** Make full name from remote name - * - * @param remote_name name of remote - * @retval full name of remote on disk - */ - std::string make_full_name(const std::string& path, const std::string& remote_name) const; public: /** Restriction to button name length. Buttons larger are ignored. */ @@ -125,9 +122,9 @@ public: * incremented digit(2,3,4,etc) added to name and check repeated. * * @param name - suggested remote name - * @retval garanteed free remote name, prefixed with suggested + * @param path - remote file path */ - std::string find_vacant_remote_name(const std::string& name); + void find_vacant_remote_name(string_t name, string_t path); /** Get button list * @@ -185,8 +182,8 @@ public: /** Load data from disk into current remote * - * @param name - name of remote to load + * @param path - path to remote file * @retval true if success, false otherwise */ - bool load(const std::string& path, const std::string& name); + bool load(string_t path); }; diff --git a/applications/infrared/scene/infrared_app_scene_edit_rename.cpp b/applications/infrared/scene/infrared_app_scene_edit_rename.cpp index dc63c64b..761da49f 100644 --- a/applications/infrared/scene/infrared_app_scene_edit_rename.cpp +++ b/applications/infrared/scene/infrared_app_scene_edit_rename.cpp @@ -1,4 +1,6 @@ #include "../infrared_app.h" +#include "m-string.h" +#include "toolbox/path.h" void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { InfraredAppViewManager* view_manager = app->get_view_manager(); @@ -21,9 +23,18 @@ void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { enter_name_length = InfraredAppRemoteManager::max_remote_name_length; text_input_set_header_text(text_input, "Name the remote"); + string_t folder_path; + string_init(folder_path); + + if(string_end_with_str_p(app->file_path, InfraredApp::infrared_extension)) { + path_extract_dirname(string_get_cstr(app->file_path), folder_path); + } + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - app->infrared_directory, app->infrared_extension, remote_name.c_str()); + string_get_cstr(folder_path), app->infrared_extension, remote_name.c_str()); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + string_clear(folder_path); } text_input_set_result_callback( diff --git a/applications/infrared/scene/infrared_app_scene_remote_list.cpp b/applications/infrared/scene/infrared_app_scene_remote_list.cpp index f59ff3e9..c72acb6e 100644 --- a/applications/infrared/scene/infrared_app_scene_remote_list.cpp +++ b/applications/infrared/scene/infrared_app_scene_remote_list.cpp @@ -1,4 +1,5 @@ #include "../infrared_app.h" +#include "assets_icons.h" #include "infrared/infrared_app_event.h" #include @@ -8,11 +9,6 @@ void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) { bool result = false; bool file_select_result; auto remote_manager = app->get_remote_manager(); - auto last_selected_remote = remote_manager->get_remote_name(); - const char* last_selected_remote_name = - last_selected_remote.size() ? last_selected_remote.c_str() : nullptr; - auto filename_ts = - std::make_unique(InfraredAppRemoteManager::max_remote_name_length); DialogsApp* dialogs = app->get_dialogs(); InfraredAppViewManager* view_manager = app->get_view_manager(); @@ -20,16 +16,17 @@ void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) { button_menu_reset(button_menu); view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu); - file_select_result = dialog_file_select_show( + file_select_result = dialog_file_browser_show( dialogs, - InfraredApp::infrared_directory, + app->file_path, + app->file_path, InfraredApp::infrared_extension, - filename_ts->text, - filename_ts->text_size, - last_selected_remote_name); + true, + &I_ir_10px, + true); if(file_select_result) { - if(remote_manager->load(InfraredApp::infrared_directory, std::string(filename_ts->text))) { + if(remote_manager->load(app->file_path)) { app->switch_to_next_scene(InfraredApp::Scene::Remote); result = true; } diff --git a/applications/infrared/scene/infrared_app_scene_start.cpp b/applications/infrared/scene/infrared_app_scene_start.cpp index c42ab9fe..5efdce7a 100644 --- a/applications/infrared/scene/infrared_app_scene_start.cpp +++ b/applications/infrared/scene/infrared_app_scene_start.cpp @@ -26,6 +26,8 @@ void InfraredAppSceneStart::on_enter(InfraredApp* app) { submenu, "Learn New Remote", SubmenuIndexLearnNewRemote, submenu_callback, app); submenu_add_item(submenu, "Saved Remotes", SubmenuIndexSavedRemotes, submenu_callback, app); submenu_set_selected_item(submenu, submenu_item_selected); + + string_set_str(app->file_path, InfraredApp::infrared_directory); submenu_item_selected = 0; view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); diff --git a/applications/lfrfid/lfrfid_app.cpp b/applications/lfrfid/lfrfid_app.cpp index c2274234..4027a07e 100644 --- a/applications/lfrfid/lfrfid_app.cpp +++ b/applications/lfrfid/lfrfid_app.cpp @@ -1,4 +1,7 @@ #include "lfrfid_app.h" +#include "assets_icons.h" +#include "furi/common_defines.h" +#include "m-string.h" #include "scene/lfrfid_app_scene_start.h" #include "scene/lfrfid_app_scene_read.h" #include "scene/lfrfid_app_scene_read_success.h" @@ -31,9 +34,11 @@ LfRfidApp::LfRfidApp() , storage{"storage"} , dialogs{"dialogs"} , text_store(40) { + string_init_set_str(file_path, app_folder); } LfRfidApp::~LfRfidApp() { + string_clear(file_path); } void LfRfidApp::run(void* _args) { @@ -42,7 +47,8 @@ void LfRfidApp::run(void* _args) { make_app_folder(); if(strlen(args)) { - load_key_data(args, &worker.key); + string_set_str(file_path, args); + load_key_data(file_path, &worker.key); scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); scene_controller.process(100, SceneType::Emulate); } else { @@ -69,65 +75,49 @@ void LfRfidApp::run(void* _args) { } bool LfRfidApp::save_key(RfidKey* key) { - string_t file_name; bool result = false; make_app_folder(); - string_init_printf(file_name, "%s/%s%s", app_folder, key->get_name(), app_extension); - result = save_key_data(string_get_cstr(file_name), key); - string_clear(file_name); + if(string_end_with_str_p(file_path, app_extension)) { + size_t filename_start = string_search_rchar(file_path, '/'); + string_left(file_path, filename_start); + } + string_cat_printf(file_path, "/%s%s", key->get_name(), app_extension); + + result = save_key_data(file_path, key); return result; } bool LfRfidApp::load_key_from_file_select(bool need_restore) { - TextStore* filename_ts = new TextStore(64); - bool result = false; - - if(need_restore) { - result = dialog_file_select_show( - dialogs, - app_folder, - app_extension, - filename_ts->text, - filename_ts->text_size, - worker.key.get_name()); - } else { - result = dialog_file_select_show( - dialogs, app_folder, app_extension, filename_ts->text, filename_ts->text_size, NULL); + if(!need_restore) { + string_set_str(file_path, app_folder); } + bool result = dialog_file_browser_show( + dialogs, file_path, file_path, app_extension, true, &I_125_10px, true); + if(result) { - string_t key_str; - string_init_printf(key_str, "%s/%s%s", app_folder, filename_ts->text, app_extension); - result = load_key_data(string_get_cstr(key_str), &worker.key); - string_clear(key_str); + result = load_key_data(file_path, &worker.key); } - delete filename_ts; return result; } bool LfRfidApp::delete_key(RfidKey* key) { - string_t file_name; - bool result = false; - - string_init_printf(file_name, "%s/%s%s", app_folder, key->get_name(), app_extension); - result = storage_simply_remove(storage, string_get_cstr(file_name)); - string_clear(file_name); - - return result; + UNUSED(key); + return storage_simply_remove(storage, string_get_cstr(file_path)); } -bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { +bool LfRfidApp::load_key_data(string_t path, RfidKey* key) { FlipperFormat* file = flipper_format_file_alloc(storage); bool result = false; string_t str_result; string_init(str_result); do { - if(!flipper_format_file_open_existing(file, path)) break; + if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break; // header uint32_t version; @@ -149,7 +139,7 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { break; loaded_key.set_data(key_data, loaded_key.get_type_data_count()); - path_extract_filename_no_ext(path, str_result); + path_extract_filename(path, str_result, true); loaded_key.set_name(string_get_cstr(str_result)); *key = loaded_key; @@ -166,12 +156,12 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { return result; } -bool LfRfidApp::save_key_data(const char* path, RfidKey* key) { +bool LfRfidApp::save_key_data(string_t path, RfidKey* key) { FlipperFormat* file = flipper_format_file_alloc(storage); bool result = false; do { - if(!flipper_format_file_open_always(file, path)) break; + if(!flipper_format_file_open_always(file, string_get_cstr(path))) break; if(!flipper_format_write_header_cstr(file, app_filetype, 1)) break; if(!flipper_format_write_comment_cstr(file, "Key type can be EM4100, H10301 or I40134")) break; diff --git a/applications/lfrfid/lfrfid_app.h b/applications/lfrfid/lfrfid_app.h index dddfb753..3f8209a1 100644 --- a/applications/lfrfid/lfrfid_app.h +++ b/applications/lfrfid/lfrfid_app.h @@ -1,4 +1,5 @@ #pragma once +#include "m-string.h" #include #include @@ -76,6 +77,8 @@ public: TextStore text_store; + string_t file_path; + void run(void* args); static const char* app_folder; @@ -86,8 +89,8 @@ public: bool load_key_from_file_select(bool need_restore); bool delete_key(RfidKey* key); - bool load_key_data(const char* path, RfidKey* key); - bool save_key_data(const char* path, RfidKey* key); + bool load_key_data(string_t path, RfidKey* key); + bool save_key_data(string_t path, RfidKey* key); void make_app_folder(); }; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp index d460724e..d7ba2c9e 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp @@ -1,11 +1,14 @@ #include "lfrfid_app_scene_save_name.h" +#include "m-string.h" #include +#include void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { const char* key_name = app->worker.key.get_name(); bool key_name_empty = !strcmp(key_name, ""); if(key_name_empty) { + string_set_str(app->file_path, app->app_folder); set_random_name(app->text_store.text, app->text_store.text_size); } else { app->text_store.set("%s", key_name); @@ -21,10 +24,17 @@ void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { app->worker.key.get_name_length(), key_name_empty); + string_t folder_path; + string_init(folder_path); + + path_extract_dirname(string_get_cstr(app->file_path), folder_path); + ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(app->app_folder, app->app_extension, key_name); + validator_is_file_alloc_init(string_get_cstr(folder_path), app->app_extension, key_name); text_input->set_validator(validator_is_file_callback, validator_is_file); + string_clear(folder_path); + app->view_controller.switch_to(); } diff --git a/applications/music_player/music_player.c b/applications/music_player/music_player.c index 6dfef492..9b5dda0f 100644 --- a/applications/music_player/music_player.c +++ b/applications/music_player/music_player.c @@ -1,3 +1,5 @@ +#include "assets_icons.h" +#include "m-string.h" #include #include @@ -298,23 +300,23 @@ int32_t music_player_app(void* p) { if(p) { string_cat_str(file_path, p); } else { - char file_name[256] = {0}; + string_set_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); + DialogsApp* dialogs = furi_record_open("dialogs"); - bool res = dialog_file_select_show( + bool res = dialog_file_browser_show( dialogs, - MUSIC_PLAYER_APP_PATH_FOLDER, + file_path, + file_path, MUSIC_PLAYER_APP_EXTENSION, - file_name, - 255, - NULL); + true, + &I_music_10px, + false); + furi_record_close("dialogs"); if(!res) { FURI_LOG_E(TAG, "No file selected"); break; } - string_cat_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); - string_cat_str(file_path, "/"); - string_cat_str(file_path, file_name); } if(!music_player_worker_load(music_player->worker, string_get_cstr(file_path))) { diff --git a/applications/nfc/nfc_device.c b/applications/nfc/nfc_device.c index 63f0c3cc..ca3fc45a 100644 --- a/applications/nfc/nfc_device.c +++ b/applications/nfc/nfc_device.c @@ -1,4 +1,6 @@ #include "nfc_device.h" +#include "assets_icons.h" +#include "m-string.h" #include "nfc_types.h" #include @@ -14,6 +16,7 @@ NfcDevice* nfc_device_alloc() { NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); nfc_dev->storage = furi_record_open("storage"); nfc_dev->dialogs = furi_record_open("dialogs"); + string_init(nfc_dev->load_path); return nfc_dev; } @@ -22,6 +25,7 @@ void nfc_device_free(NfcDevice* nfc_dev) { nfc_device_clear(nfc_dev); furi_record_close("storage"); furi_record_close("dialogs"); + string_clear(nfc_dev->load_path); free(nfc_dev); } @@ -730,11 +734,24 @@ void nfc_device_set_name(NfcDevice* dev, const char* name) { strlcpy(dev->dev_name, name, NFC_DEV_NAME_MAX_LEN); } +static void nfc_device_get_path_without_ext(string_t orig_path, string_t shadow_path) { + // TODO: this won't work if there is ".nfc" anywhere in the path other than + // at the end + size_t ext_start = string_search_str(orig_path, NFC_APP_EXTENSION); + string_set_n(shadow_path, orig_path, 0, ext_start); +} + +static void nfc_device_get_shadow_path(string_t orig_path, string_t shadow_path) { + nfc_device_get_path_without_ext(orig_path, shadow_path); + string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); +} + static bool nfc_device_save_file( NfcDevice* dev, const char* dev_name, const char* folder, - const char* extension) { + const char* extension, + bool use_load_path) { furi_assert(dev); bool saved = false; @@ -744,10 +761,19 @@ static bool nfc_device_save_file( string_init(temp_str); do { - // Create nfc directory if necessary - if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; - // First remove nfc device file if it was saved - string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); + if(use_load_path && !string_empty_p(dev->load_path)) { + // Get directory name + path_extract_dirname(string_get_cstr(dev->load_path), temp_str); + // Create nfc directory if necessary + if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break; + // Make path to file to save + string_cat_printf(temp_str, "/%s%s", dev_name, extension); + } else { + // Create nfc directory if necessary + if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; + // First remove nfc device file if it was saved + string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); + } // Open file if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break; // Write header @@ -786,12 +812,12 @@ static bool nfc_device_save_file( } bool nfc_device_save(NfcDevice* dev, const char* dev_name) { - return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION); + return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION, true); } bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) { dev->shadow_file_exist = true; - return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION); + return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION, true); } static bool nfc_device_load_data(NfcDevice* dev, string_t path) { @@ -805,9 +831,7 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) { do { // Check existance of shadow file - size_t ext_start = string_search_str(path, NFC_APP_EXTENSION); - string_set_n(temp_str, path, 0, ext_start); - string_cat_printf(temp_str, "%s", NFC_APP_SHADOW_EXTENSION); + nfc_device_get_shadow_path(path, temp_str); dev->shadow_file_exist = storage_common_stat(dev->storage, string_get_cstr(temp_str), NULL) == FSE_OK; // Open shadow file if it exists. If not - open original @@ -864,15 +888,16 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { furi_assert(file_path); // Load device data - string_t path; - string_init_set_str(path, file_path); - bool dev_load = nfc_device_load_data(dev, path); + string_set_str(dev->load_path, file_path); + bool dev_load = nfc_device_load_data(dev, dev->load_path); if(dev_load) { // Set device name - path_extract_filename_no_ext(file_path, path); - nfc_device_set_name(dev, string_get_cstr(path)); + string_t filename; + string_init(filename); + path_extract_filename_no_ext(file_path, filename); + nfc_device_set_name(dev, string_get_cstr(filename)); + string_clear(filename); } - string_clear(path); return dev_load; } @@ -880,23 +905,19 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { bool nfc_file_select(NfcDevice* dev) { furi_assert(dev); - // Input events and views are managed by file_select - bool res = dialog_file_select_show( - dev->dialogs, - NFC_APP_FOLDER, - NFC_APP_EXTENSION, - dev->file_name, - sizeof(dev->file_name), - dev->dev_name); + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + dev->dialogs, dev->load_path, dev->load_path, NFC_APP_EXTENSION, true, &I_Nfc_10px, true); if(res) { - string_t dev_str; - // Get key file path - string_init_printf(dev_str, "%s/%s%s", NFC_APP_FOLDER, dev->file_name, NFC_APP_EXTENSION); - res = nfc_device_load_data(dev, dev_str); + string_t filename; + string_init(filename); + path_extract_filename(dev->load_path, filename, true); + strncpy(dev->dev_name, string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN); + res = nfc_device_load_data(dev, dev->load_path); if(res) { - nfc_device_set_name(dev, dev->file_name); + nfc_device_set_name(dev, dev->dev_name); } - string_clear(dev_str); + string_clear(filename); } return res; @@ -914,9 +935,10 @@ void nfc_device_clear(NfcDevice* dev) { nfc_device_data_clear(&dev->dev_data); memset(&dev->dev_data, 0, sizeof(dev->dev_data)); dev->format = NfcDeviceSaveFormatUid; + string_set_str(dev->load_path, NFC_APP_FOLDER); } -bool nfc_device_delete(NfcDevice* dev) { +bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { furi_assert(dev); bool deleted = false; @@ -925,12 +947,20 @@ bool nfc_device_delete(NfcDevice* dev) { do { // Delete original file - string_init_printf(file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + if(use_load_path && !string_empty_p(dev->load_path)) { + string_set(file_path, dev->load_path); + } else { + string_printf(file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + } if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; // Delete shadow file if it exists if(dev->shadow_file_exist) { - string_printf( - file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + if(use_load_path && !string_empty_p(dev->load_path)) { + nfc_device_get_shadow_path(dev->load_path, file_path); + } else { + string_printf( + file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + } if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; } deleted = true; @@ -944,19 +974,29 @@ bool nfc_device_delete(NfcDevice* dev) { return deleted; } -bool nfc_device_restore(NfcDevice* dev) { +bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { furi_assert(dev); furi_assert(dev->shadow_file_exist); bool restored = false; string_t path; + string_init(path); + do { - string_init_printf( - path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + if(use_load_path && !string_empty_p(dev->load_path)) { + nfc_device_get_shadow_path(dev->load_path, path); + } else { + string_printf( + path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + } if(!storage_simply_remove(dev->storage, string_get_cstr(path))) break; dev->shadow_file_exist = false; - string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + if(use_load_path && !string_empty_p(dev->load_path)) { + string_set(path, dev->load_path); + } else { + string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + } if(!nfc_device_load_data(dev, path)) break; restored = true; } while(0); diff --git a/applications/nfc/nfc_device.h b/applications/nfc/nfc_device.h index 215f637f..400f6c36 100644 --- a/applications/nfc/nfc_device.h +++ b/applications/nfc/nfc_device.h @@ -12,7 +12,6 @@ #include #define NFC_DEV_NAME_MAX_LEN 22 -#define NFC_FILE_NAME_MAX_LEN 120 #define NFC_READER_DATA_MAX_SIZE 64 #define NFC_APP_FOLDER "/any/nfc" @@ -57,7 +56,7 @@ typedef struct { DialogsApp* dialogs; NfcDeviceData dev_data; char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; - char file_name[NFC_FILE_NAME_MAX_LEN]; + string_t load_path; NfcDeviceSaveFormat format; bool shadow_file_exist; } NfcDevice; @@ -80,6 +79,6 @@ void nfc_device_data_clear(NfcDeviceData* dev); void nfc_device_clear(NfcDevice* dev); -bool nfc_device_delete(NfcDevice* dev); +bool nfc_device_delete(NfcDevice* dev, bool use_load_path); -bool nfc_device_restore(NfcDevice* dev); +bool nfc_device_restore(NfcDevice* dev, bool use_load_path); diff --git a/applications/nfc/scenes/nfc_scene_delete.c b/applications/nfc/scenes/nfc_scene_delete.c index e8ba3e44..1946b929 100755 --- a/applications/nfc/scenes/nfc_scene_delete.c +++ b/applications/nfc/scenes/nfc_scene_delete.c @@ -73,7 +73,7 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { if(event.event == GuiButtonTypeLeft) { return scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == GuiButtonTypeRight) { - if(nfc_device_delete(nfc->dev)) { + if(nfc_device_delete(nfc->dev, true)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); } else { scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/nfc/scenes/nfc_scene_save_name.c b/applications/nfc/scenes/nfc_scene_save_name.c old mode 100755 new mode 100644 index e95c97eb..d5e05472 --- a/applications/nfc/scenes/nfc_scene_save_name.c +++ b/applications/nfc/scenes/nfc_scene_save_name.c @@ -1,6 +1,8 @@ #include "../nfc_i.h" +#include "m-string.h" #include #include +#include void nfc_scene_save_name_text_input_callback(void* context) { Nfc* nfc = context; @@ -29,11 +31,22 @@ void nfc_scene_save_name_on_enter(void* context) { NFC_DEV_NAME_MAX_LEN, dev_name_empty); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(NFC_APP_FOLDER, NFC_APP_EXTENSION, nfc->dev->dev_name); + string_t folder_path; + string_init(folder_path); + + if(string_end_with_str_p(nfc->dev->load_path, NFC_APP_EXTENSION)) { + path_extract_dirname(string_get_cstr(nfc->dev->load_path), folder_path); + } else { + string_set_str(folder_path, NFC_APP_FOLDER); + } + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + string_get_cstr(folder_path), NFC_APP_EXTENSION, nfc->dev->dev_name); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextInput); + + string_clear(folder_path); } bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { @@ -43,7 +56,7 @@ bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventTextInputDone) { if(strcmp(nfc->dev->dev_name, "")) { - nfc_device_delete(nfc->dev); + nfc_device_delete(nfc->dev, true); } if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetUid)) { nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; diff --git a/applications/nfc/scenes/nfc_scene_save_success.c b/applications/nfc/scenes/nfc_scene_save_success.c index 985897a6..5c15a509 100644 --- a/applications/nfc/scenes/nfc_scene_save_success.c +++ b/applications/nfc/scenes/nfc_scene_save_success.c @@ -30,9 +30,6 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneCardMenu)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneCardMenu); - } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - consumed = scene_manager_search_and_switch_to_another_scene( - nfc->scene_manager, NfcSceneFileSelect); } else if(scene_manager_has_previous_scene( nfc->scene_manager, NfcSceneMifareDesfireMenu)) { consumed = scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/nfc/scenes/nfc_scene_saved_menu.c b/applications/nfc/scenes/nfc_scene_saved_menu.c index 1b435ccd..f2b2dea3 100644 --- a/applications/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/nfc/scenes/nfc_scene_saved_menu.c @@ -78,7 +78,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); consumed = true; } else if(event.event == SubmenuIndexRestoreOriginal) { - if(!nfc_device_restore(nfc->dev)) { + if(!nfc_device_restore(nfc->dev, true)) { scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneStart); } else { diff --git a/applications/nfc/scenes/nfc_scene_set_type.c b/applications/nfc/scenes/nfc_scene_set_type.c index 0dbb4f7e..0fe63424 100755 --- a/applications/nfc/scenes/nfc_scene_set_type.c +++ b/applications/nfc/scenes/nfc_scene_set_type.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include "m-string.h" enum SubmenuIndex { SubmenuIndexNFCA4, @@ -16,6 +17,7 @@ void nfc_scene_set_type_on_enter(void* context) { Submenu* submenu = nfc->submenu; // Clear device name nfc_device_set_name(nfc->dev, ""); + string_set_str(nfc->dev->load_path, ""); submenu_add_item( submenu, "NFC-A 7-bytes UID", SubmenuIndexNFCA7, nfc_scene_set_type_submenu_callback, nfc); submenu_add_item( diff --git a/applications/subghz/scenes/subghz_scene_delete.c b/applications/subghz/scenes/subghz_scene_delete.c index 83d6d475..43151de2 100644 --- a/applications/subghz/scenes/subghz_scene_delete.c +++ b/applications/subghz/scenes/subghz_scene_delete.c @@ -49,7 +49,7 @@ bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneDelete) { - strncpy(subghz->file_path_tmp, subghz->file_path, SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path_tmp, subghz->file_path); if(subghz_delete_file(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); } else { diff --git a/applications/subghz/scenes/subghz_scene_delete_raw.c b/applications/subghz/scenes/subghz_scene_delete_raw.c index 03f0eb81..a20968d5 100644 --- a/applications/subghz/scenes/subghz_scene_delete_raw.c +++ b/applications/subghz/scenes/subghz_scene_delete_raw.c @@ -24,7 +24,7 @@ void subghz_scene_delete_raw_on_enter(void* context) { char delete_str[SUBGHZ_MAX_LEN_NAME + 16]; string_t file_name; string_init(file_name); - path_extract_filename_no_ext(subghz->file_path, file_name); + path_extract_filename(subghz->file_path, file_name, true); snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", string_get_cstr(file_name)); string_clear(file_name); @@ -61,7 +61,7 @@ bool subghz_scene_delete_raw_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneDeleteRAW) { - strncpy(subghz->file_path_tmp, subghz->file_path, SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path_tmp, subghz->file_path); if(subghz_delete_file(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); } else { diff --git a/applications/subghz/scenes/subghz_scene_more_raw.c b/applications/subghz/scenes/subghz_scene_more_raw.c index 54bd0815..a5bade92 100644 --- a/applications/subghz/scenes/subghz_scene_more_raw.c +++ b/applications/subghz/scenes/subghz_scene_more_raw.c @@ -45,7 +45,7 @@ bool subghz_scene_more_raw_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteRAW); return true; } else if(event.event == SubmenuIndexEdit) { - memset(subghz->file_path_tmp, 0, sizeof(subghz->file_path_tmp)); + string_reset(subghz->file_path_tmp); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexEdit); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/subghz/scenes/subghz_scene_read_raw.c index 767f0924..191a7285 100644 --- a/applications/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/subghz/scenes/subghz_scene_read_raw.c @@ -23,7 +23,7 @@ bool subghz_scene_read_raw_update_filename(SubGhz* subghz) { break; } - strncpy(subghz->file_path, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path, temp_str); ret = true; } while(false); @@ -73,13 +73,13 @@ void subghz_scene_read_raw_on_enter(void* context) { subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, ""); break; case SubGhzRxKeyStateRAWLoad: - path_extract_filename_no_ext(subghz->file_path, file_name); + path_extract_filename(subghz->file_path, file_name, true); subghz_read_raw_set_status( subghz->subghz_read_raw, SubGhzReadRAWStatusLoadKeyTX, string_get_cstr(file_name)); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; break; case SubGhzRxKeyStateRAWSave: - path_extract_filename_no_ext(subghz->file_path, file_name); + path_extract_filename(subghz->file_path, file_name, true); subghz_read_raw_set_status( subghz->subghz_read_raw, SubGhzReadRAWStatusSaveKey, string_get_cstr(file_name)); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; @@ -273,7 +273,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz->state_notifications = SubGhzNotificationStateRx; subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; } else { - string_set(subghz->error_str, "Function requires\nan SD card."); + string_set_str(subghz->error_str, "Function requires\nan SD card."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } } diff --git a/applications/subghz/scenes/subghz_scene_save_name.c b/applications/subghz/scenes/subghz_scene_save_name.c index 617709cc..95fc1938 100644 --- a/applications/subghz/scenes/subghz_scene_save_name.c +++ b/applications/subghz/scenes/subghz_scene_save_name.c @@ -1,4 +1,6 @@ #include "../subghz_i.h" +#include "m-string.h" +#include "subghz/types.h" #include #include "../helpers/subghz_custom_event.h" #include @@ -20,45 +22,49 @@ void subghz_scene_save_name_on_enter(void* context) { bool dev_name_empty = false; string_t file_name; + string_t dir_name; string_init(file_name); + string_init(dir_name); - if(!strcmp(subghz->file_path, "")) { + if(!subghz_path_is_file(subghz->file_path)) { char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0}; set_random_name(file_name_buf, SUBGHZ_MAX_LEN_NAME); - string_set(file_name, file_name_buf); - strncpy(subghz->file_dir, SUBGHZ_APP_FOLDER, SUBGHZ_MAX_LEN_NAME); + string_set_str(file_name, file_name_buf); + string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); //highlighting the entire filename by default dev_name_empty = true; } else { - strncpy(subghz->file_path_tmp, subghz->file_path, SUBGHZ_MAX_LEN_NAME); - path_extract_dirname(subghz->file_path, file_name); - strncpy(subghz->file_dir, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); - path_extract_filename_no_ext(subghz->file_path, file_name); + string_set(subghz->file_path_tmp, subghz->file_path); + path_extract_dirname(string_get_cstr(subghz->file_path), dir_name); + path_extract_filename(subghz->file_path, file_name, true); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerNoSet) { subghz_get_next_name_file(subghz, SUBGHZ_MAX_LEN_NAME); - path_extract_filename_no_ext(subghz->file_path, file_name); + path_extract_filename(subghz->file_path, file_name, true); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) == SubGhzCustomEventManagerSetRAW) { dev_name_empty = true; } } + string_set(subghz->file_path, dir_name); } - strncpy(subghz->file_path, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); + + strncpy(subghz->file_name_tmp, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); text_input_set_header_text(text_input, "Name signal"); text_input_set_result_callback( text_input, subghz_scene_save_name_text_input_callback, subghz, - subghz->file_path, + subghz->file_name_tmp, MAX_TEXT_INPUT_LEN, // buffer size dev_name_empty); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(subghz->file_dir, SUBGHZ_APP_EXTENSION, NULL); + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + string_get_cstr(subghz->file_path), SUBGHZ_APP_EXTENSION, NULL); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); string_clear(file_name); + string_clear(dir_name); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTextInput); } @@ -66,18 +72,15 @@ void subghz_scene_save_name_on_enter(void* context) { bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeBack) { - strncpy(subghz->file_path, subghz->file_path_tmp, SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path, subghz->file_path_tmp); scene_manager_previous_scene(subghz->scene_manager); return true; } else if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneSaveName) { - if(strcmp(subghz->file_path, "")) { - string_t temp_str; - string_init_printf( - temp_str, "%s/%s%s", subghz->file_dir, subghz->file_path, SUBGHZ_APP_EXTENSION); - strncpy(subghz->file_path, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); - string_clear(temp_str); - if(strcmp(subghz->file_path_tmp, "")) { + if(strcmp(subghz->file_name_tmp, "")) { + string_cat_printf( + subghz->file_path, "/%s%s", subghz->file_name_tmp, SUBGHZ_APP_EXTENSION); + if(subghz_path_is_file(subghz->file_path_tmp)) { if(!subghz_rename_file(subghz)) { return false; } @@ -85,7 +88,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType) != SubGhzCustomEventManagerNoSet) { subghz_save_protocol_to_file( - subghz, subghz->txrx->fff_data, subghz->file_path); + subghz, subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneSetType, @@ -95,13 +98,14 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { subghz, subghz_history_get_raw_data( subghz->txrx->history, subghz->txrx->idx_menu_chosen), - subghz->file_path); + string_get_cstr(subghz->file_path)); } } if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerNoSet) { - subghz_protocol_raw_gen_fff_data(subghz->txrx->fff_data, subghz->file_path); + subghz_protocol_raw_gen_fff_data( + subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); } else { @@ -111,7 +115,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess); return true; } else { - string_set(subghz->error_str, "No name file"); + string_set_str(subghz->error_str, "No name file"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); return true; } diff --git a/applications/subghz/scenes/subghz_scene_set_type.c b/applications/subghz/scenes/subghz_scene_set_type.c index 9dad8cd9..cb56ff74 100644 --- a/applications/subghz/scenes/subghz_scene_set_type.c +++ b/applications/subghz/scenes/subghz_scene_set_type.c @@ -39,7 +39,7 @@ bool subghz_scene_set_type_submenu_gen_data_protocol( subghz_receiver_search_decoder_base_by_name(subghz->txrx->receiver, protocol_name); if(subghz->txrx->decoder_result == NULL) { - string_set(subghz->error_str, "Protocol not found"); + string_set_str(subghz->error_str, "Protocol not found"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); return false; } @@ -282,7 +282,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { } subghz_transmitter_free(subghz->txrx->transmitter); if(!generated_protocol) { - string_set( + string_set_str( subghz->error_str, "Function requires\nan SD card with\nfresh databases."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } @@ -306,7 +306,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { } subghz_transmitter_free(subghz->txrx->transmitter); if(!generated_protocol) { - string_set( + string_set_str( subghz->error_str, "Function requires\nan SD card with\nfresh databases."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } diff --git a/applications/subghz/scenes/subghz_scene_transmitter.c b/applications/subghz/scenes/subghz_scene_transmitter.c index a7244424..552a36e9 100644 --- a/applications/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/subghz/scenes/subghz_scene_transmitter.c @@ -94,7 +94,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneStart); return true; } else if(event.event == SubGhzCustomEventViewTransmitterError) { - string_set(subghz->error_str, "Protocol not found"); + string_set_str(subghz->error_str, "Protocol not found"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); } } else if(event.type == SceneManagerEventTypeTick) { diff --git a/applications/subghz/subghz.c b/applications/subghz/subghz.c index f597adde..d9dc19f4 100644 --- a/applications/subghz/subghz.c +++ b/applications/subghz/subghz.c @@ -1,5 +1,7 @@ /* Abandon hope, all ye who enter here. */ +#include "m-string.h" +#include "subghz/types.h" #include "subghz_i.h" #include @@ -24,6 +26,9 @@ void subghz_tick_event_callback(void* context) { SubGhz* subghz_alloc() { SubGhz* subghz = malloc(sizeof(SubGhz)); + string_init(subghz->file_path); + string_init(subghz->file_path_tmp); + // GUI subghz->gui = furi_record_open("gui"); @@ -241,9 +246,9 @@ void subghz_free(SubGhz* subghz) { furi_record_close("notification"); subghz->notifications = NULL; - // About birds - furi_assert(subghz->file_path[SUBGHZ_MAX_LEN_NAME] == 0); - furi_assert(subghz->file_path_tmp[SUBGHZ_MAX_LEN_NAME] == 0); + // Path strings + string_clear(subghz->file_path); + string_clear(subghz->file_path_tmp); // The rest free(subghz); @@ -260,7 +265,7 @@ int32_t subghz_app(void* p) { // Check argument and run corresponding scene if(p) { if(subghz_key_load(subghz, p)) { - strncpy(subghz->file_path, p, SUBGHZ_MAX_LEN_NAME); + string_set_str(subghz->file_path, p); if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { //Load Raw TX @@ -276,12 +281,13 @@ int32_t subghz_app(void* p) { view_dispatcher_stop(subghz->view_dispatcher); } } else { + string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); if(load_database) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart); } else { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneShowError, SubGhzCustomEventManagerSet); - string_set( + string_set_str( subghz->error_str, "No SD card or\ndatabase found.\nSome app function\nmay be reduced."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); diff --git a/applications/subghz/subghz_cli.c b/applications/subghz/subghz_cli.c index f919e1cf..6c92a5d6 100644 --- a/applications/subghz/subghz_cli.c +++ b/applications/subghz/subghz_cli.c @@ -303,7 +303,7 @@ void subghz_cli_command_decode_raw(Cli* cli, string_t args, void* context) { UNUSED(context); string_t file_name; string_init(file_name); - string_set(file_name, "/any/subghz/test.sub"); + string_set_str(file_name, "/any/subghz/test.sub"); Storage* storage = furi_record_open("storage"); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); diff --git a/applications/subghz/subghz_history.c b/applications/subghz/subghz_history.c index cb30726b..a8f86eec 100644 --- a/applications/subghz/subghz_history.c +++ b/applications/subghz/subghz_history.c @@ -169,14 +169,14 @@ bool subghz_history_add_to_history( break; } if(!strcmp(string_get_cstr(instance->tmp_string), "KeeLoq")) { - string_set(instance->tmp_string, "KL "); + string_set_str(instance->tmp_string, "KL "); if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { FURI_LOG_E(TAG, "Missing Protocol"); break; } string_cat(instance->tmp_string, text); } else if(!strcmp(string_get_cstr(instance->tmp_string), "Star Line")) { - string_set(instance->tmp_string, "SL "); + string_set_str(instance->tmp_string, "SL "); if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { FURI_LOG_E(TAG, "Missing Protocol"); break; diff --git a/applications/subghz/subghz_i.c b/applications/subghz/subghz_i.c index ff22b429..81017a8b 100644 --- a/applications/subghz/subghz_i.c +++ b/applications/subghz/subghz_i.c @@ -1,5 +1,8 @@ #include "subghz_i.h" +#include "assets_icons.h" +#include "m-string.h" +#include "subghz/types.h" #include #include #include @@ -45,11 +48,11 @@ void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_ if(modulation != NULL) { if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { - string_set(modulation, "AM"); + string_set_str(modulation, "AM"); } else if( subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev238Async || subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev476Async) { - string_set(modulation, "FM"); + string_set_str(modulation, "FM"); } else { furi_crash("SugGhz: Modulation is incorrect."); } @@ -189,8 +192,9 @@ void subghz_tx_stop(SubGhz* subghz) { //if protocol dynamic then we save the last upload if((subghz->txrx->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) && - (strcmp(subghz->file_path, ""))) { - subghz_save_protocol_to_file(subghz, subghz->txrx->fff_data, subghz->file_path); + (subghz_path_is_file(subghz->file_path))) { + subghz_save_protocol_to_file( + subghz, subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); } subghz_idle(subghz); notification_message(subghz->notifications, &sequence_reset_red); @@ -332,10 +336,10 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { bool res = false; - if(strcmp(subghz->file_path, "")) { + if(subghz_path_is_file(subghz->file_path)) { //get the name of the next free file - path_extract_filename_no_ext(subghz->file_path, file_name); - path_extract_dirname(subghz->file_path, file_path); + path_extract_filename(subghz->file_path, file_name, true); + path_extract_dirname(string_get_cstr(subghz->file_path), file_path); storage_get_next_filename( storage, @@ -351,7 +355,7 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { string_get_cstr(file_path), string_get_cstr(file_name), SUBGHZ_APP_EXTENSION); - strncpy(subghz->file_path, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path, temp_str); res = true; } @@ -411,19 +415,17 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { string_init(file_path); // Input events and views are managed by file_select - bool res = dialog_file_select_show( + bool res = dialog_file_browser_show( subghz->dialogs, - SUBGHZ_APP_FOLDER, - SUBGHZ_APP_EXTENSION, subghz->file_path, - sizeof(subghz->file_path), - NULL); + subghz->file_path, + SUBGHZ_APP_EXTENSION, + true, + &I_sub1_10px, + true); if(res) { - string_printf( - file_path, "%s/%s%s", SUBGHZ_APP_FOLDER, subghz->file_path, SUBGHZ_APP_EXTENSION); - strncpy(subghz->file_path, string_get_cstr(file_path), SUBGHZ_MAX_LEN_NAME); - res = subghz_key_load(subghz, subghz->file_path); + res = subghz_key_load(subghz, string_get_cstr(subghz->file_path)); } string_clear(file_path); @@ -437,9 +439,9 @@ bool subghz_rename_file(SubGhz* subghz) { Storage* storage = furi_record_open("storage"); - if(strcmp(subghz->file_path_tmp, subghz->file_path)) { - FS_Error fs_result = - storage_common_rename(storage, subghz->file_path_tmp, subghz->file_path); + if(string_cmp(subghz->file_path_tmp, subghz->file_path)) { + FS_Error fs_result = storage_common_rename( + storage, string_get_cstr(subghz->file_path_tmp), string_get_cstr(subghz->file_path)); if(fs_result != FSE_OK) { dialog_message_show_storage_error(subghz->dialogs, "Cannot rename\n file/directory"); @@ -455,7 +457,7 @@ bool subghz_delete_file(SubGhz* subghz) { furi_assert(subghz); Storage* storage = furi_record_open("storage"); - bool result = storage_simply_remove(storage, subghz->file_path_tmp); + bool result = storage_simply_remove(storage, string_get_cstr(subghz->file_path_tmp)); furi_record_close("storage"); subghz_file_name_clear(subghz); @@ -465,8 +467,12 @@ bool subghz_delete_file(SubGhz* subghz) { void subghz_file_name_clear(SubGhz* subghz) { furi_assert(subghz); - memset(subghz->file_path, 0, sizeof(subghz->file_path)); - memset(subghz->file_path_tmp, 0, sizeof(subghz->file_path_tmp)); + string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); + string_reset(subghz->file_path_tmp); +} + +bool subghz_path_is_file(string_t path) { + return string_end_with_str_p(path, SUBGHZ_APP_EXTENSION); } uint32_t subghz_random_serial(void) { diff --git a/applications/subghz/subghz_i.h b/applications/subghz/subghz_i.h index 1e917af5..5d1b0699 100644 --- a/applications/subghz/subghz_i.h +++ b/applications/subghz/subghz_i.h @@ -35,7 +35,7 @@ #include #include -#define SUBGHZ_MAX_LEN_NAME 250 +#define SUBGHZ_MAX_LEN_NAME 64 /** SubGhzNotification state */ typedef enum { @@ -119,10 +119,9 @@ struct SubGhz { TextInput* text_input; Widget* widget; DialogsApp* dialogs; - char file_path[SUBGHZ_MAX_LEN_NAME + 1]; - char file_path_tmp[SUBGHZ_MAX_LEN_NAME + 1]; - //ToDo you can get rid of it, you need to refactor text input to return the path to the folder - char file_dir[SUBGHZ_MAX_LEN_NAME + 1]; + string_t file_path; + string_t file_path_tmp; + char file_name_tmp[SUBGHZ_MAX_LEN_NAME]; SubGhzNotificationState state_notifications; SubGhzViewReceiver* subghz_receiver; @@ -173,5 +172,6 @@ bool subghz_load_protocol_from_file(SubGhz* subghz); bool subghz_rename_file(SubGhz* subghz); bool subghz_delete_file(SubGhz* subghz); void subghz_file_name_clear(SubGhz* subghz); +bool subghz_path_is_file(string_t path); uint32_t subghz_random_serial(void); void subghz_hopper_update(SubGhz* subghz); diff --git a/applications/subghz/views/receiver.c b/applications/subghz/views/receiver.c index 7b19cbcf..866d8272 100644 --- a/applications/subghz/views/receiver.c +++ b/applications/subghz/views/receiver.c @@ -111,9 +111,9 @@ void subghz_view_receiver_add_data_statusbar( furi_assert(subghz_receiver); with_view_model( subghz_receiver->view, (SubGhzViewReceiverModel * model) { - string_set(model->frequency_str, frequency_str); - string_set(model->preset_str, preset_str); - string_set(model->history_stat_str, history_stat_str); + string_set_str(model->frequency_str, frequency_str); + string_set_str(model->preset_str, preset_str); + string_set_str(model->history_stat_str, history_stat_str); return true; }); } diff --git a/applications/subghz/views/subghz_read_raw.c b/applications/subghz/views/subghz_read_raw.c index ff3ba45a..a4807d77 100644 --- a/applications/subghz/views/subghz_read_raw.c +++ b/applications/subghz/views/subghz_read_raw.c @@ -46,8 +46,8 @@ void subghz_read_raw_add_data_statusbar( furi_assert(instance); with_view_model( instance->view, (SubGhzReadRAWModel * model) { - string_set(model->frequency_str, frequency_str); - string_set(model->preset_str, preset_str); + string_set_str(model->frequency_str, frequency_str); + string_set_str(model->preset_str, preset_str); return true; }); } @@ -372,7 +372,7 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { model->satus = SubGhzReadRAWStatusStart; model->rssi_history_end = false; model->ind_write = 0; - string_set(model->sample_write, "0 spl."); + string_set_str(model->sample_write, "0 spl."); string_reset(model->file_name); instance->callback(SubGhzCustomEventViewReadRAWErase, instance->context); } @@ -424,7 +424,7 @@ void subghz_read_raw_set_status( model->rssi_history_end = false; model->ind_write = 0; string_reset(model->file_name); - string_set(model->sample_write, "0 spl."); + string_set_str(model->sample_write, "0 spl."); return true; }); break; @@ -441,8 +441,8 @@ void subghz_read_raw_set_status( model->satus = SubGhzReadRAWStatusLoadKeyIDLE; model->rssi_history_end = false; model->ind_write = 0; - string_set(model->file_name, file_name); - string_set(model->sample_write, "RAW"); + string_set_str(model->file_name, file_name); + string_set_str(model->sample_write, "RAW"); return true; }); break; @@ -451,8 +451,8 @@ void subghz_read_raw_set_status( instance->view, (SubGhzReadRAWModel * model) { model->satus = SubGhzReadRAWStatusLoadKeyIDLE; if(!model->ind_write) { - string_set(model->file_name, file_name); - string_set(model->sample_write, "RAW"); + string_set_str(model->file_name, file_name); + string_set_str(model->sample_write, "RAW"); } else { string_reset(model->file_name); } diff --git a/applications/subghz/views/transmitter.c b/applications/subghz/views/transmitter.c index 3113e31f..3cbcf098 100644 --- a/applications/subghz/views/transmitter.c +++ b/applications/subghz/views/transmitter.c @@ -36,9 +36,9 @@ void subghz_view_transmitter_add_data_to_show( furi_assert(subghz_transmitter); with_view_model( subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { - string_set(model->key_str, key_str); - string_set(model->frequency_str, frequency_str); - string_set(model->preset_str, preset_str); + string_set_str(model->key_str, key_str); + string_set_str(model->frequency_str, frequency_str); + string_set_str(model->preset_str, preset_str); model->show_button = show_button; return true; }); diff --git a/assets/compiled/assets_icons.c b/assets/compiled/assets_icons.c index 9046dddd..fb125095 100644 --- a/assets/compiled/assets_icons.c +++ b/assets/compiled/assets_icons.c @@ -40,6 +40,9 @@ const uint8_t* const _I_125_10px[] = {_I_125_10px_0}; const uint8_t _I_Nfc_10px_0[] = {0x00,0x80,0x00,0x00,0x01,0x22,0x02,0x43,0x02,0x45,0x02,0x49,0x02,0x31,0x02,0x22,0x02,0x00,0x01,0x80,0x00,}; const uint8_t* const _I_Nfc_10px[] = {_I_Nfc_10px_0}; +const uint8_t _I_back_10px_0[] = {0x00,0x00,0x00,0x10,0x00,0x38,0x00,0x7C,0x00,0xFE,0x00,0x38,0x00,0x38,0x00,0xF8,0x01,0xF8,0x01,0x00,0x00,}; +const uint8_t* const _I_back_10px[] = {_I_back_10px_0}; + const uint8_t _I_badusb_10px_0[] = {0x01,0x00,0x11,0x00,0x00,0x0f,0xe2,0x01,0xfc,0x80,0xdd,0x20,0x32,0x48,0x08,0x14,0x40,0x23,0xa8,0x08,0xa0,}; const uint8_t* const _I_badusb_10px[] = {_I_badusb_10px_0}; @@ -55,6 +58,12 @@ const uint8_t* const _I_ibutt_10px[] = {_I_ibutt_10px_0}; const uint8_t _I_ir_10px_0[] = {0x00,0xFC,0x00,0x02,0x01,0x79,0x02,0x84,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x58,0x00,0x78,0x00,0xFF,0x03,}; const uint8_t* const _I_ir_10px[] = {_I_ir_10px_0}; +const uint8_t _I_loading_10px_0[] = {0x00,0xFE,0x00,0x82,0x00,0xBA,0x00,0x54,0x00,0x28,0x00,0x28,0x00,0x44,0x00,0x92,0x00,0xBA,0x00,0xFE,0x00,}; +const uint8_t* const _I_loading_10px[] = {_I_loading_10px_0}; + +const uint8_t _I_music_10px_0[] = {0x01,0x00,0x10,0x00,0xf0,0x00,0x46,0x03,0x20,0x80,0x00,0x4e,0x7d,0x00,0x9f,0x80,0x4a,0x3c,0x13,0x20,}; +const uint8_t* const _I_music_10px[] = {_I_music_10px_0}; + const uint8_t _I_sub1_10px_0[] = {0x01,0x00,0x12,0x00,0x81,0x40,0x69,0x30,0x2c,0x2c,0x0b,0x6a,0x01,0x28,0x0c,0x0a,0x65,0x01,0x98,0x40,0x00,0x26,}; const uint8_t* const _I_sub1_10px[] = {_I_sub1_10px_0}; @@ -648,11 +657,14 @@ const Icon A_Levelup1_128x64 = {.width=128,.height=64,.frame_count=11,.frame_rat const Icon A_Levelup2_128x64 = {.width=128,.height=64,.frame_count=11,.frame_rate=2,.frames=_A_Levelup2_128x64}; const Icon I_125_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_125_10px}; const Icon I_Nfc_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_Nfc_10px}; +const Icon I_back_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_back_10px}; const Icon I_badusb_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_badusb_10px}; const Icon I_ble_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ble_10px}; const Icon I_dir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_dir_10px}; const Icon I_ibutt_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ibutt_10px}; const Icon I_ir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ir_10px}; +const Icon I_loading_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_loading_10px}; +const Icon I_music_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_music_10px}; const Icon I_sub1_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_sub1_10px}; const Icon I_u2f_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_u2f_10px}; const Icon I_unknown_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_unknown_10px}; diff --git a/assets/compiled/assets_icons.h b/assets/compiled/assets_icons.h index 64cd3d36..45947880 100644 --- a/assets/compiled/assets_icons.h +++ b/assets/compiled/assets_icons.h @@ -7,11 +7,14 @@ extern const Icon A_Levelup1_128x64; extern const Icon A_Levelup2_128x64; extern const Icon I_125_10px; extern const Icon I_Nfc_10px; +extern const Icon I_back_10px; extern const Icon I_badusb_10px; extern const Icon I_ble_10px; extern const Icon I_dir_10px; extern const Icon I_ibutt_10px; extern const Icon I_ir_10px; +extern const Icon I_loading_10px; +extern const Icon I_music_10px; extern const Icon I_sub1_10px; extern const Icon I_u2f_10px; extern const Icon I_unknown_10px; diff --git a/assets/icons/Archive/back_10px.png b/assets/icons/Archive/back_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..008efe82fa129f55c8efa77688dd507f31398896 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih`sfGge++G4X{LmR$r2S$euShDcma{&W9q1G|A@v4nS_xAT(=AqI)BoYRaGO@;Li wag`sfGge++QC**pItze8KAtX)ArhBs`vdt77;ren`<{QJxM$1n6@^=3kBM_gs5bkj zOgNacP$Dkno^tQ4ONX;p$w=sbzP$Pyzt8HuHJfiox2e6^5wrUCr{B&G7(ZT+pZ{3J RO%!M@gQu&X%Q~loCIH;wIT`=} literal 0 HcmV?d00001 diff --git a/assets/icons/Archive/music_10px.png b/assets/icons/Archive/music_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..d41eb0db8c822c60be6c097393b3682680b81a6c GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xGmzZ=C-xtZVhivIasA%~WHIdey2}7aaTa() z7Bet#3xhBt!>l_x9Asc&(0a_=e)W&n8K5!-Pgg&ebxsLQ0Ao%f>i_@% literal 0 HcmV?d00001 diff --git a/lib/one_wire/ibutton/ibutton_key.c b/lib/one_wire/ibutton/ibutton_key.c index c6d4466f..2c0f7fa2 100644 --- a/lib/one_wire/ibutton/ibutton_key.c +++ b/lib/one_wire/ibutton/ibutton_key.c @@ -4,7 +4,6 @@ struct iButtonKey { uint8_t data[IBUTTON_KEY_DATA_SIZE]; - char name[IBUTTON_KEY_NAME_SIZE]; iButtonKeyType type; }; @@ -42,14 +41,6 @@ uint8_t ibutton_key_get_data_size(iButtonKey* key) { return ibutton_key_get_size_by_type(key->type); } -void ibutton_key_set_name(iButtonKey* key, const char* name) { - strlcpy(key->name, name, IBUTTON_KEY_NAME_SIZE); -} - -const char* ibutton_key_get_name_p(iButtonKey* key) { - return key->name; -} - void ibutton_key_set_type(iButtonKey* key, iButtonKeyType key_type) { key->type = key_type; } diff --git a/lib/one_wire/ibutton/ibutton_key.h b/lib/one_wire/ibutton/ibutton_key.h index 8d6732bc..f66537d7 100644 --- a/lib/one_wire/ibutton/ibutton_key.h +++ b/lib/one_wire/ibutton/ibutton_key.h @@ -68,20 +68,6 @@ const uint8_t* ibutton_key_get_data_p(iButtonKey* key); */ uint8_t ibutton_key_get_data_size(iButtonKey* key); -/** - * Set key name - * @param key - * @param name - */ -void ibutton_key_set_name(iButtonKey* key, const char* name); - -/** - * Get pointer to key name - * @param key - * @return const char* - */ -const char* ibutton_key_get_name_p(iButtonKey* key); - /** * Set key type * @param key diff --git a/lib/toolbox/path.c b/lib/toolbox/path.c index 4fd042e4..a99e57d1 100644 --- a/lib/toolbox/path.c +++ b/lib/toolbox/path.c @@ -19,6 +19,20 @@ void path_extract_filename_no_ext(const char* path, string_t filename) { string_mid(filename, start_position, end_position - start_position); } +void path_extract_filename(string_t path, string_t name, bool trim_ext) { + size_t filename_start = string_search_rchar(path, '/'); + if(filename_start > 0) { + filename_start++; + string_set_n(name, path, filename_start, string_size(path) - filename_start); + } + if(trim_ext) { + size_t dot = string_search_rchar(name, '.'); + if(dot > 0) { + string_left(name, dot); + } + } +} + static inline void path_cleanup(string_t path) { string_strim(path); while(string_end_with_str_p(path, "/")) { diff --git a/lib/toolbox/path.h b/lib/toolbox/path.h index 0de63bb2..76e501cc 100644 --- a/lib/toolbox/path.h +++ b/lib/toolbox/path.h @@ -14,6 +14,15 @@ extern "C" { */ void path_extract_filename_no_ext(const char* path, string_t filename); +/** + * @brief Extract filename string from path. + * + * @param path path string + * @param filename output filename string. Must be initialized before. + * @param trim_ext true - get filename without extension + */ +void path_extract_filename(string_t path, string_t filename, bool trim_ext); + /** * @brief Extract last path component * From 5c45250dd2c60f2f3a38849a6253f58a71059598 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 27 May 2022 15:29:52 +0400 Subject: [PATCH 21/28] =?UTF-8?q?SubGhz:=20=D0=A1reating=20and=20deliverin?= =?UTF-8?q?g=20Security+=201.0=20(#1268)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: fix frequency reset on incomplete exit from the transmission menu * SubGhz: fix incorrect shutdown of CC1101 if it was not turned on * SubGhz: consciousness and transmission support Security+ 1.0 * Unit_test: add test encoder Security+ 1/0 * SubGhz: fix start counter Security+ 1.0 Co-authored-by: SG Co-authored-by: あく --- .../subghz/scenes/subghz_scene_set_type.c | 46 +++ .../subghz/scenes/subghz_scene_transmitter.c | 3 - applications/subghz/subghz_i.c | 4 +- applications/unit_tests/subghz/subghz_test.c | 7 + assets/unit_tests/subghz/security_pls_1_0.sub | 7 + lib/subghz/protocols/secplus_v1.c | 263 +++++++++++++++++- lib/subghz/protocols/secplus_v1.h | 41 +++ 7 files changed, 360 insertions(+), 11 deletions(-) create mode 100644 assets/unit_tests/subghz/security_pls_1_0.sub diff --git a/applications/subghz/scenes/subghz_scene_set_type.c b/applications/subghz/scenes/subghz_scene_set_type.c index cb56ff74..b050107d 100644 --- a/applications/subghz/scenes/subghz_scene_set_type.c +++ b/applications/subghz/scenes/subghz_scene_set_type.c @@ -1,5 +1,6 @@ #include "../subghz_i.h" #include +#include #include #include #include @@ -21,6 +22,8 @@ enum SubmenuIndex { SubmenuIndexDoorHan_315_00, SubmenuIndexDoorHan_433_92, SubmenuIndexFirefly_300_00, + SubmenuIndexLiftMaster_315_00, + SubmenuIndexLiftMaster_390_00, }; bool subghz_scene_set_type_submenu_gen_data_protocol( @@ -142,6 +145,18 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexDoorHan_433_92, subghz_scene_set_type_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "LiftMaster_315", + SubmenuIndexLiftMaster_315_00, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "LiftMaster_390", + SubmenuIndexLiftMaster_390_00, + subghz_scene_set_type_submenu_callback, + subghz); submenu_set_selected_item( subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType)); @@ -311,6 +326,37 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } break; + case SubmenuIndexLiftMaster_315_00: + while(!subghz_protocol_secplus_v1_check_fixed(key)) { + key = subghz_random_serial(); + } + + if(subghz_scene_set_type_submenu_gen_data_protocol( + subghz, + SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, + (uint64_t)key << 32 | 0xE6000000, + 42, + 315000000, + FuriHalSubGhzPresetOok650Async)) { + generated_protocol = true; + } + break; + case SubmenuIndexLiftMaster_390_00: + while(!subghz_protocol_secplus_v1_check_fixed(key)) { + key = subghz_random_serial(); + } + + if(subghz_scene_set_type_submenu_gen_data_protocol( + subghz, + SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, + (uint64_t)key << 32 | 0xE6000000, + 42, + 390000000, + FuriHalSubGhzPresetOok650Async)) { + generated_protocol = true; + } + break; + default: return false; break; diff --git a/applications/subghz/scenes/subghz_scene_transmitter.c b/applications/subghz/scenes/subghz_scene_transmitter.c index 552a36e9..b8b22749 100644 --- a/applications/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/subghz/scenes/subghz_scene_transmitter.c @@ -108,8 +108,5 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { void subghz_scene_transmitter_on_exit(void* context) { SubGhz* subghz = context; - //Restore default setting - subghz->txrx->frequency = subghz_setting_get_default_frequency(subghz->setting); - subghz->txrx->preset = FuriHalSubGhzPresetOok650Async; subghz->state_notifications = SubGhzNotificationStateIDLE; } diff --git a/applications/subghz/subghz_i.c b/applications/subghz/subghz_i.c index 81017a8b..5439fd2a 100644 --- a/applications/subghz/subghz_i.c +++ b/applications/subghz/subghz_i.c @@ -174,7 +174,9 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { } if(!ret) { subghz_transmitter_free(subghz->txrx->transmitter); - subghz_idle(subghz); + if(subghz->txrx->txrx_state != SubGhzTxRxStateSleep) { + subghz_idle(subghz); + } } } while(false); diff --git a/applications/unit_tests/subghz/subghz_test.c b/applications/unit_tests/subghz/subghz_test.c index 879deae8..e0f59a7e 100644 --- a/applications/unit_tests/subghz/subghz_test.c +++ b/applications/unit_tests/subghz/subghz_test.c @@ -416,6 +416,12 @@ MU_TEST(subghz_encoder_holtek_test) { "Test encoder " SUBGHZ_PROTOCOL_HOLTEK_NAME " error\r\n"); } +MU_TEST(subghz_encoder_secplus_v1_test) { + mu_assert( + subghz_encoder_test("/ext/unit_tests/subghz/security_pls_1_0.sub"), + "Test encoder " SUBGHZ_PROTOCOL_SECPLUS_V1_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -457,6 +463,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_firefly_test); MU_RUN_TEST(subghz_encoder_megacode_test); MU_RUN_TEST(subghz_encoder_holtek_test); + MU_RUN_TEST(subghz_encoder_secplus_v1_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/assets/unit_tests/subghz/security_pls_1_0.sub b/assets/unit_tests/subghz/security_pls_1_0.sub new file mode 100644 index 00000000..f0909a77 --- /dev/null +++ b/assets/unit_tests/subghz/security_pls_1_0.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 310000000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Security+ 1.0 +Bit: 42 +Key: 1C 41 D2 39 E6 A3 8B CC diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index ff43e2a4..b51179f4 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -48,6 +48,8 @@ struct SubGhzProtocolEncoderSecPlus_v1 { SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; + + uint8_t data_array[44]; }; typedef enum { @@ -71,23 +73,258 @@ const SubGhzProtocolDecoder subghz_protocol_secplus_v1_decoder = { }; const SubGhzProtocolEncoder subghz_protocol_secplus_v1_encoder = { - .alloc = NULL, - .free = NULL, + .alloc = subghz_protocol_encoder_secplus_v1_alloc, + .free = subghz_protocol_encoder_secplus_v1_free, - .deserialize = NULL, - .stop = NULL, - .yield = NULL, + .deserialize = subghz_protocol_encoder_secplus_v1_deserialize, + .stop = subghz_protocol_encoder_secplus_v1_stop, + .yield = subghz_protocol_encoder_secplus_v1_yield, }; const SubGhzProtocol subghz_protocol_secplus_v1 = { .name = SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, .type = SubGhzProtocolTypeDynamic, - .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send, .decoder = &subghz_protocol_secplus_v1_decoder, .encoder = &subghz_protocol_secplus_v1_encoder, }; +void* subghz_protocol_encoder_secplus_v1_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderSecPlus_v1* instance = malloc(sizeof(SubGhzProtocolEncoderSecPlus_v1)); + + instance->base.protocol = &subghz_protocol_secplus_v1; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_secplus_v1_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v1* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + * @return true On success + */ +static bool + subghz_protocol_encoder_secplus_v1_get_upload(SubGhzProtocolEncoderSecPlus_v1* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Encoder size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header packet 1 + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * (116 + 3)); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + + //Send data packet 1 + for(uint8_t i = SECPLUS_V1_PACKET_1_INDEX_BASE + 1; i < SECPLUS_V1_PACKET_1_INDEX_BASE + 21; + i++) { + switch(instance->data_array[i]) { + case SECPLUS_V1_BIT_0: + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + break; + case SECPLUS_V1_BIT_1: + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 2); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 2); + break; + case SECPLUS_V1_BIT_2: + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + break; + + default: + FURI_LOG_E(TAG, "Encoder error, wrong bit type"); + return false; + break; + } + } + + //Send header packet 2 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * (116)); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + + //Send data packet 2 + for(uint8_t i = SECPLUS_V1_PACKET_2_INDEX_BASE + 1; i < SECPLUS_V1_PACKET_2_INDEX_BASE + 21; + i++) { + switch(instance->data_array[i]) { + case SECPLUS_V1_BIT_0: + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + break; + case SECPLUS_V1_BIT_1: + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 2); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 2); + break; + case SECPLUS_V1_BIT_2: + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v1_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_secplus_v1_const.te_short * 3); + break; + + default: + FURI_LOG_E(TAG, "Encoder error, wrong bit type."); + return false; + break; + } + } + + return true; +} + +/** + * Security+ 1.0 message encoding + * @param instance SubGhzProtocolEncoderSecPlus_v1* + */ + +static bool subghz_protocol_secplus_v1_encode(SubGhzProtocolEncoderSecPlus_v1* instance) { + uint32_t fixed = (instance->generic.data >> 32) & 0xFFFFFFFF; + uint32_t rolling = instance->generic.data & 0xFFFFFFFF; + + uint8_t rolling_array[20] = {0}; + uint8_t fixed_array[20] = {0}; + uint32_t acc = 0; + + //increment the counter + rolling += 2; + + //update data + instance->generic.data &= 0xFFFFFFFF00000000; + instance->generic.data |= rolling; + + if(rolling > 0xFFFFFFFF) { + rolling = 0xE6000000; + } + if(fixed > 0xCFD41B90) { + FURI_LOG_E("TAG", "Encode wrong fixed data"); + return false; + } + + rolling = subghz_protocol_blocks_reverse_key(rolling, 32); + + for(int i = 19; i > -1; i--) { + rolling_array[i] = rolling % 3; + rolling /= 3; + fixed_array[i] = fixed % 3; + fixed /= 3; + } + + instance->data_array[SECPLUS_V1_PACKET_1_INDEX_BASE] = SECPLUS_V1_PACKET_1_HEADER; + instance->data_array[SECPLUS_V1_PACKET_2_INDEX_BASE] = SECPLUS_V1_PACKET_2_HEADER; + + //encode packet 1 + for(uint8_t i = 1; i < 11; i++) { + acc += rolling_array[i - 1]; + instance->data_array[i * 2 - 1] = rolling_array[i - 1]; + acc += fixed_array[i - 1]; + instance->data_array[i * 2] = acc % 3; + } + + acc = 0; + //encode packet 2 + for(uint8_t i = 11; i < 21; i++) { + acc += rolling_array[i - 1]; + instance->data_array[i * 2] = rolling_array[i - 1]; + acc += fixed_array[i - 1]; + instance->data_array[i * 2 + 1] = acc % 3; + } + + return true; +} + +bool subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v1* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_secplus_v1_encode(instance)) { + break; + } + if(!subghz_protocol_encoder_secplus_v1_get_upload(instance)) { + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_secplus_v1_stop(void* context) { + SubGhzProtocolEncoderSecPlus_v1* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_secplus_v1_yield(void* context) { + SubGhzProtocolEncoderSecPlus_v1* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + void* subghz_protocol_decoder_secplus_v1_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolDecoderSecPlus_v1* instance = malloc(sizeof(SubGhzProtocolDecoderSecPlus_v1)); @@ -110,7 +347,7 @@ void subghz_protocol_decoder_secplus_v1_reset(void* context) { } /** - * Security+ 1.0 half-message decoding + * Security+ 1.0 message decoding * @param instance SubGhzProtocolDecoderSecPlus_v1* */ @@ -291,6 +528,18 @@ bool subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat return subghz_block_generic_deserialize(&instance->generic, flipper_format); } +bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed) { + //uint8_t id0 = (fixed / 3) % 3; + uint8_t id1 = (fixed / 9) % 3; + uint8_t btn = fixed % 3; + + do { + if(id1 == 0) return false; + if(!(btn == 0 || btn == 1 || btn == 2)) return false; + } while(false); + return true; +} + void subghz_protocol_decoder_secplus_v1_get_string(void* context, string_t output) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v1* instance = context; diff --git a/lib/subghz/protocols/secplus_v1.h b/lib/subghz/protocols/secplus_v1.h index 891f751c..1c752df7 100644 --- a/lib/subghz/protocols/secplus_v1.h +++ b/lib/subghz/protocols/secplus_v1.h @@ -10,6 +10,40 @@ extern const SubGhzProtocolDecoder subghz_protocol_secplus_v1_decoder; extern const SubGhzProtocolEncoder subghz_protocol_secplus_v1_encoder; extern const SubGhzProtocol subghz_protocol_secplus_v1; +/** + * Allocate SubGhzProtocolEncoderSecPlus_v1. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderSecPlus_v1* pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + */ +void* subghz_protocol_encoder_secplus_v1_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderSecPlus_v1. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + */ +void subghz_protocol_encoder_secplus_v1_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + */ +void subghz_protocol_encoder_secplus_v1_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v1 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_secplus_v1_yield(void* context); + /** * Allocate SubGhzProtocolDecoderSecPlus_v1. * @param environment Pointer to a SubGhzEnvironment instance @@ -66,6 +100,13 @@ bool subghz_protocol_decoder_secplus_v1_serialize( */ bool subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format); +/** + * Validation of fixed parts SubGhzProtocolDecoderSecPlus_v1. + * @param fixed fixed parts + * @return true On success + */ +bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); + /** * Getting a textual representation of the received data. * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance From 67fbefbe63a606a7eae44ce847f0495c85dec9ab Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 27 May 2022 16:19:20 +0400 Subject: [PATCH 22/28] =?UTF-8?q?SubGhz:=20=D0=A1reating=20and=20deliverin?= =?UTF-8?q?g=20Security+=202.0=20(#1273)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: Security+ 2.0 "Add manually" option * SubGhz: fix message error * Unit_test: add Security+ 2.0 encoder * Applications: remove obsolete code * SubGhz: save menu position in "Add Manually" menu Co-authored-by: Aleksandr Kutuzov --- applications/applications.c | 8 - .../scene/scened_app_scene_byte_input.cpp | 35 - .../scene/scened_app_scene_byte_input.h | 19 - .../scene/scened_app_scene_start.cpp | 47 -- .../scene/scened_app_scene_start.h | 13 - .../scened_app_example/scened_app.cpp | 20 - applications/scened_app_example/scened_app.h | 47 -- .../scened_app_launcher.cpp | 11 - .../subghz/scenes/subghz_scene_set_type.c | 83 +- applications/unit_tests/subghz/subghz_test.c | 7 + assets/unit_tests/subghz/security_pls_2_0.sub | 8 + lib/subghz/protocols/came_twee.c | 3 +- lib/subghz/protocols/secplus_v2.c | 726 +++++++++++++----- lib/subghz/protocols/secplus_v2.h | 55 ++ 14 files changed, 689 insertions(+), 393 deletions(-) delete mode 100644 applications/scened_app_example/scene/scened_app_scene_byte_input.cpp delete mode 100644 applications/scened_app_example/scene/scened_app_scene_byte_input.h delete mode 100644 applications/scened_app_example/scene/scened_app_scene_start.cpp delete mode 100644 applications/scened_app_example/scene/scened_app_scene_start.h delete mode 100644 applications/scened_app_example/scened_app.cpp delete mode 100644 applications/scened_app_example/scened_app.h delete mode 100644 applications/scened_app_example/scened_app_launcher.cpp create mode 100644 assets/unit_tests/subghz/security_pls_2_0.sub diff --git a/applications/applications.c b/applications/applications.c index 2ed1b961..dccab589 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -420,14 +420,6 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { .flags = FlipperApplicationFlagDefault}, #endif -#ifdef APP_SCENED - {.app = scened_app, - .name = "Templated Scene", - .stack_size = 1024, - .icon = NULL, - .flags = FlipperApplicationFlagDefault}, -#endif - #ifdef APP_LF_RFID {.app = lfrfid_debug_app, .name = "LF-RFID Debug", diff --git a/applications/scened_app_example/scene/scened_app_scene_byte_input.cpp b/applications/scened_app_example/scene/scened_app_scene_byte_input.cpp deleted file mode 100644 index 200252ad..00000000 --- a/applications/scened_app_example/scene/scened_app_scene_byte_input.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "scened_app_scene_byte_input.h" - -void ScenedAppSceneByteInput::on_enter(ScenedApp* app, bool /* need_restore */) { - ByteInputVM* byte_input = app->view_controller; - auto callback = cbc::obtain_connector(this, &ScenedAppSceneByteInput::result_callback); - - byte_input->set_result_callback(callback, NULL, app, data, 4); - byte_input->set_header_text("Enter the key"); - - app->view_controller.switch_to(); -} - -bool ScenedAppSceneByteInput::on_event(ScenedApp* app, ScenedApp::Event* event) { - bool consumed = false; - - if(event->type == ScenedApp::EventType::ByteEditResult) { - app->scene_controller.switch_to_previous_scene(); - consumed = true; - } - - return consumed; -} - -void ScenedAppSceneByteInput::on_exit(ScenedApp* app) { - app->view_controller.get()->clean(); -} - -void ScenedAppSceneByteInput::result_callback(void* context) { - ScenedApp* app = static_cast(context); - ScenedApp::Event event; - - event.type = ScenedApp::EventType::ByteEditResult; - - app->view_controller.send_event(&event); -} diff --git a/applications/scened_app_example/scene/scened_app_scene_byte_input.h b/applications/scened_app_example/scene/scened_app_scene_byte_input.h deleted file mode 100644 index 0f0b02ac..00000000 --- a/applications/scened_app_example/scene/scened_app_scene_byte_input.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include "../scened_app.h" - -class ScenedAppSceneByteInput : public GenericScene { -public: - void on_enter(ScenedApp* app, bool need_restore) final; - bool on_event(ScenedApp* app, ScenedApp::Event* event) final; - void on_exit(ScenedApp* app) final; - -private: - void result_callback(void* context); - - uint8_t data[4] = { - 0x01, - 0xA2, - 0xF4, - 0xD3, - }; -}; diff --git a/applications/scened_app_example/scene/scened_app_scene_start.cpp b/applications/scened_app_example/scene/scened_app_scene_start.cpp deleted file mode 100644 index 5538962b..00000000 --- a/applications/scened_app_example/scene/scened_app_scene_start.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "scened_app_scene_start.h" - -typedef enum { - SubmenuByteInput, -} SubmenuIndex; - -void ScenedAppSceneStart::on_enter(ScenedApp* app, bool need_restore) { - auto submenu = app->view_controller.get(); - auto callback = cbc::obtain_connector(this, &ScenedAppSceneStart::submenu_callback); - - submenu->add_item("Byte Input", SubmenuByteInput, callback, app); - - if(need_restore) { - submenu->set_selected_item(submenu_item_selected); - } - app->view_controller.switch_to(); -} - -bool ScenedAppSceneStart::on_event(ScenedApp* app, ScenedApp::Event* event) { - bool consumed = false; - - if(event->type == ScenedApp::EventType::MenuSelected) { - submenu_item_selected = event->payload.menu_index; - switch(event->payload.menu_index) { - case SubmenuByteInput: - app->scene_controller.switch_to_next_scene(ScenedApp::SceneType::ByteInputScene); - break; - } - consumed = true; - } - - return consumed; -} - -void ScenedAppSceneStart::on_exit(ScenedApp* app) { - app->view_controller.get()->clean(); -} - -void ScenedAppSceneStart::submenu_callback(void* context, uint32_t index) { - ScenedApp* app = static_cast(context); - ScenedApp::Event event; - - event.type = ScenedApp::EventType::MenuSelected; - event.payload.menu_index = index; - - app->view_controller.send_event(&event); -} diff --git a/applications/scened_app_example/scene/scened_app_scene_start.h b/applications/scened_app_example/scene/scened_app_scene_start.h deleted file mode 100644 index 8324a20f..00000000 --- a/applications/scened_app_example/scene/scened_app_scene_start.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "../scened_app.h" - -class ScenedAppSceneStart : public GenericScene { -public: - void on_enter(ScenedApp* app, bool need_restore) final; - bool on_event(ScenedApp* app, ScenedApp::Event* event) final; - void on_exit(ScenedApp* app) final; - -private: - void submenu_callback(void* context, uint32_t index); - uint32_t submenu_item_selected = 0; -}; diff --git a/applications/scened_app_example/scened_app.cpp b/applications/scened_app_example/scened_app.cpp deleted file mode 100644 index 64040b22..00000000 --- a/applications/scened_app_example/scened_app.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "scened_app.h" -#include "scene/scened_app_scene_start.h" -#include "scene/scened_app_scene_byte_input.h" - -ScenedApp::ScenedApp() - : scene_controller{this} - , text_store{128} - , notification{"notification"} { -} - -ScenedApp::~ScenedApp() { -} - -void ScenedApp::run() { - scene_controller.add_scene(SceneType::Start, new ScenedAppSceneStart()); - scene_controller.add_scene(SceneType::ByteInputScene, new ScenedAppSceneByteInput()); - - notification_message(notification, &sequence_blink_green_10); - scene_controller.process(100); -} diff --git a/applications/scened_app_example/scened_app.h b/applications/scened_app_example/scened_app.h deleted file mode 100644 index c6cecb47..00000000 --- a/applications/scened_app_example/scened_app.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include - -class ScenedApp { -public: - enum class EventType : uint8_t { - GENERIC_EVENT_ENUM_VALUES, - MenuSelected, - ByteEditResult, - }; - - enum class SceneType : uint8_t { - GENERIC_SCENE_ENUM_VALUES, - ByteInputScene, - }; - - class Event { - public: - union { - int32_t menu_index; - } payload; - - EventType type; - }; - - SceneController, ScenedApp> scene_controller; - TextStore text_store; - ViewController view_controller; - RecordController notification; - - ~ScenedApp(); - ScenedApp(); - - void run(); -}; diff --git a/applications/scened_app_example/scened_app_launcher.cpp b/applications/scened_app_example/scened_app_launcher.cpp deleted file mode 100644 index 3d0bdfca..00000000 --- a/applications/scened_app_example/scened_app_launcher.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "scened_app.h" - -// app enter function -extern "C" int32_t scened_app(void* p) { - UNUSED(p); - ScenedApp* app = new ScenedApp(); - app->run(); - delete app; - - return 0; -} diff --git a/applications/subghz/scenes/subghz_scene_set_type.c b/applications/subghz/scenes/subghz_scene_set_type.c index b050107d..b64d0023 100644 --- a/applications/subghz/scenes/subghz_scene_set_type.c +++ b/applications/subghz/scenes/subghz_scene_set_type.c @@ -1,6 +1,7 @@ #include "../subghz_i.h" #include #include +#include #include #include #include @@ -24,6 +25,9 @@ enum SubmenuIndex { SubmenuIndexFirefly_300_00, SubmenuIndexLiftMaster_315_00, SubmenuIndexLiftMaster_390_00, + SubmenuIndexSecPlus_v2_310_00, + SubmenuIndexSecPlus_v2_315_00, + SubmenuIndexSecPlus_v2_390_00, }; bool subghz_scene_set_type_submenu_gen_data_protocol( @@ -157,6 +161,24 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexLiftMaster_390_00, subghz_scene_set_type_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "Security+2.0_310", + SubmenuIndexSecPlus_v2_310_00, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "Security+2.0_315", + SubmenuIndexSecPlus_v2_315_00, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "Security+2.0_390", + SubmenuIndexSecPlus_v2_390_00, + subghz_scene_set_type_submenu_callback, + subghz); submenu_set_selected_item( subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType)); @@ -330,7 +352,6 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { while(!subghz_protocol_secplus_v1_check_fixed(key)) { key = subghz_random_serial(); } - if(subghz_scene_set_type_submenu_gen_data_protocol( subghz, SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, @@ -345,7 +366,6 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { while(!subghz_protocol_secplus_v1_check_fixed(key)) { key = subghz_random_serial(); } - if(subghz_scene_set_type_submenu_gen_data_protocol( subghz, SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, @@ -356,17 +376,70 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { generated_protocol = true; } break; - + case SubmenuIndexSecPlus_v2_310_00: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); + if(subghz->txrx->transmitter) { + subghz_protocol_secplus_v2_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + key, + 0x68, + 0xE500000, + 310000000, + FuriHalSubGhzPresetOok650Async); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + break; + case SubmenuIndexSecPlus_v2_315_00: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); + if(subghz->txrx->transmitter) { + subghz_protocol_secplus_v2_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + key, + 0x68, + 0xE500000, + 315000000, + FuriHalSubGhzPresetOok650Async); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + break; + case SubmenuIndexSecPlus_v2_390_00: + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); + if(subghz->txrx->transmitter) { + subghz_protocol_secplus_v2_create_data( + subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), + subghz->txrx->fff_data, + key, + 0x68, + 0xE500000, + 390000000, + FuriHalSubGhzPresetOok650Async); + generated_protocol = true; + } else { + generated_protocol = false; + } + subghz_transmitter_free(subghz->txrx->transmitter); + break; default: return false; break; } + scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneSetType, event.event); + if(generated_protocol) { subghz_file_name_clear(subghz); DOLPHIN_DEED(DolphinDeedSubGhzAddManually); - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneSetType, SubGhzCustomEventManagerSet); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); return true; } diff --git a/applications/unit_tests/subghz/subghz_test.c b/applications/unit_tests/subghz/subghz_test.c index e0f59a7e..9e5b6599 100644 --- a/applications/unit_tests/subghz/subghz_test.c +++ b/applications/unit_tests/subghz/subghz_test.c @@ -422,6 +422,12 @@ MU_TEST(subghz_encoder_secplus_v1_test) { "Test encoder " SUBGHZ_PROTOCOL_SECPLUS_V1_NAME " error\r\n"); } +MU_TEST(subghz_encoder_secplus_v2_test) { + mu_assert( + subghz_encoder_test("/ext/unit_tests/subghz/security_pls_2_0.sub"), + "Test encoder " SUBGHZ_PROTOCOL_SECPLUS_V2_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -464,6 +470,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_megacode_test); MU_RUN_TEST(subghz_encoder_holtek_test); MU_RUN_TEST(subghz_encoder_secplus_v1_test); + MU_RUN_TEST(subghz_encoder_secplus_v2_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/assets/unit_tests/subghz/security_pls_2_0.sub b/assets/unit_tests/subghz/security_pls_2_0.sub new file mode 100644 index 00000000..a13ab0fc --- /dev/null +++ b/assets/unit_tests/subghz/security_pls_2_0.sub @@ -0,0 +1,8 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 315000000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Security+ 2.0 +Bit: 62 +Key: 00 00 3D 29 0F B9 BE EE +Secplus_packet_1: 00 00 3C 01 6B 19 DD 60 diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 4ca11623..e5fb12d1 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -145,8 +145,7 @@ static LevelDuration break; default: - FURI_LOG_E(TAG, "DO CRASH HERE."); - furi_crash(NULL); + furi_crash("SugGhz: ManchesterEncoderResult is incorrect."); break; } return level_duration_make(data.level, data.duration); diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 936eb0a6..af4de934 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -1,5 +1,6 @@ #include "secplus_v2.h" #include +#include #include "../blocks/const.h" #include "../blocks/decoder.h" #include "../blocks/encoder.h" @@ -42,6 +43,7 @@ struct SubGhzProtocolEncoderSecPlus_v2 { SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; + uint64_t secplus_packet_1; }; typedef enum { @@ -63,23 +65,555 @@ const SubGhzProtocolDecoder subghz_protocol_secplus_v2_decoder = { }; const SubGhzProtocolEncoder subghz_protocol_secplus_v2_encoder = { - .alloc = NULL, - .free = NULL, + .alloc = subghz_protocol_encoder_secplus_v2_alloc, + .free = subghz_protocol_encoder_secplus_v2_free, - .deserialize = NULL, - .stop = NULL, - .yield = NULL, + .deserialize = subghz_protocol_encoder_secplus_v2_deserialize, + .stop = subghz_protocol_encoder_secplus_v2_stop, + .yield = subghz_protocol_encoder_secplus_v2_yield, }; const SubGhzProtocol subghz_protocol_secplus_v2 = { .name = SUBGHZ_PROTOCOL_SECPLUS_V2_NAME, .type = SubGhzProtocolTypeDynamic, - .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send, .decoder = &subghz_protocol_secplus_v2_decoder, .encoder = &subghz_protocol_secplus_v2_encoder, }; +void* subghz_protocol_encoder_secplus_v2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderSecPlus_v2* instance = malloc(sizeof(SubGhzProtocolEncoderSecPlus_v2)); + + instance->base.protocol = &subghz_protocol_secplus_v2; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_secplus_v2_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v2* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static bool subghz_protocol_secplus_v2_mix_invet(uint8_t invert, uint16_t p[]) { + // selectively invert buffers + switch(invert) { + case 0x00: // 0b0000 (True, True, False), + p[0] = ~p[0] & 0x03FF; + p[1] = ~p[1] & 0x03FF; + break; + case 0x01: // 0b0001 (False, True, False), + p[1] = ~p[1] & 0x03FF; + break; + case 0x02: // 0b0010 (False, False, True), + p[2] = ~p[2] & 0x03FF; + break; + case 0x04: // 0b0100 (True, True, True), + p[0] = ~p[0] & 0x03FF; + p[1] = ~p[1] & 0x03FF; + p[2] = ~p[2] & 0x03FF; + break; + case 0x05: // 0b0101 (True, False, True), + case 0x0a: // 0b1010 (True, False, True), + p[0] = ~p[0] & 0x03FF; + p[2] = ~p[2] & 0x03FF; + break; + case 0x06: // 0b0110 (False, True, True), + p[1] = ~p[1] & 0x03FF; + p[2] = ~p[2] & 0x03FF; + break; + case 0x08: // 0b1000 (True, False, False), + p[0] = ~p[0] & 0x03FF; + break; + case 0x09: // 0b1001 (False, False, False), + break; + default: + FURI_LOG_E(TAG, "Invert FAIL"); + return false; + } + return true; +} + +static bool subghz_protocol_secplus_v2_mix_order_decode(uint8_t order, uint16_t p[]) { + uint16_t a = p[0], b = p[1], c = p[2]; + + // selectively reorder buffers + switch(order) { + case 0x06: // 0b0110 2, 1, 0], + case 0x09: // 0b1001 2, 1, 0], + p[2] = a; + p[1] = b; + p[0] = c; + break; + case 0x08: // 0b1000 1, 2, 0], + case 0x04: // 0b0100 1, 2, 0], + p[1] = a; + p[2] = b; + p[0] = c; + break; + case 0x01: // 0b0001 2, 0, 1], + p[2] = a; + p[0] = b; + p[1] = c; + break; + case 0x00: // 0b0000 0, 2, 1], + p[0] = a; + p[2] = b; + p[1] = c; + break; + case 0x05: // 0b0101 1, 0, 2], + p[1] = a; + p[0] = b; + p[2] = c; + break; + case 0x02: // 0b0010 0, 1, 2], + case 0x0A: // 0b1010 0, 1, 2], + p[0] = a; + p[1] = b; + p[2] = c; + break; + default: + FURI_LOG_E(TAG, "Order FAIL"); + return false; + } + return true; +} + +static bool subghz_protocol_secplus_v2_mix_order_encode(uint8_t order, uint16_t p[]) { + uint16_t a, b, c; + + // selectively reorder buffers + switch(order) { + case 0x06: // 0b0110 2, 1, 0], + case 0x09: // 0b1001 2, 1, 0], + a = p[2]; + b = p[1]; + c = p[0]; + break; + case 0x08: // 0b1000 1, 2, 0], + case 0x04: // 0b0100 1, 2, 0], + a = p[1]; + b = p[2]; + c = p[0]; + break; + case 0x01: // 0b0001 2, 0, 1], + a = p[2]; + b = p[0]; + c = p[1]; + break; + case 0x00: // 0b0000 0, 2, 1], + a = p[0]; + b = p[2]; + c = p[1]; + break; + case 0x05: // 0b0101 1, 0, 2], + a = p[1]; + b = p[0]; + c = p[2]; + break; + case 0x02: // 0b0010 0, 1, 2], + case 0x0A: // 0b1010 0, 1, 2], + a = p[0]; + b = p[1]; + c = p[2]; + break; + default: + FURI_LOG_E(TAG, "Order FAIL"); + return false; + } + + p[0] = a; + p[1] = b; + p[2] = c; + return true; +} + +/** + * Security+ 2.0 half-message decoding + * @param data data + * @param roll_array[] return roll_array part + * @param fixed[] return fixed part + * @return true On success + */ + +static bool + subghz_protocol_secplus_v2_decode_half(uint64_t data, uint8_t roll_array[], uint32_t* fixed) { + uint8_t order = (data >> 34) & 0x0f; + uint8_t invert = (data >> 30) & 0x0f; + uint16_t p[3] = {0}; + + for(int i = 29; i >= 0; i -= 3) { + p[0] = p[0] << 1 | bit_read(data, i); + p[1] = p[1] << 1 | bit_read(data, i - 1); + p[2] = p[2] << 1 | bit_read(data, i - 2); + } + + if(!subghz_protocol_secplus_v2_mix_invet(invert, p)) return false; + if(!subghz_protocol_secplus_v2_mix_order_decode(order, p)) return false; + + data = order << 4 | invert; + int k = 0; + for(int i = 6; i >= 0; i -= 2) { + roll_array[k++] = (data >> i) & 0x03; + if(roll_array[k] == 3) { + FURI_LOG_E(TAG, "Roll_Array FAIL"); + return false; + } + } + + for(int i = 8; i >= 0; i -= 2) { + roll_array[k++] = (p[2] >> i) & 0x03; + if(roll_array[k] == 3) { + FURI_LOG_E(TAG, "Roll_Array FAIL"); + return false; + } + } + + fixed[0] = p[0] << 10 | p[1]; + return true; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param packet_1 first part of the message + */ +static void + subghz_protocol_secplus_v2_remote_controller(SubGhzBlockGeneric* instance, uint64_t packet_1) { + uint32_t fixed_1[1]; + uint8_t roll_1[9] = {0}; + uint32_t fixed_2[1]; + uint8_t roll_2[9] = {0}; + uint8_t rolling_digits[18] = {0}; + + if(subghz_protocol_secplus_v2_decode_half(packet_1, roll_1, fixed_1) && + subghz_protocol_secplus_v2_decode_half(instance->data, roll_2, fixed_2)) { + rolling_digits[0] = roll_2[8]; + rolling_digits[1] = roll_1[8]; + + rolling_digits[2] = roll_2[4]; + rolling_digits[3] = roll_2[5]; + rolling_digits[4] = roll_2[6]; + rolling_digits[5] = roll_2[7]; + + rolling_digits[6] = roll_1[4]; + rolling_digits[7] = roll_1[5]; + rolling_digits[8] = roll_1[6]; + rolling_digits[9] = roll_1[7]; + + rolling_digits[10] = roll_2[0]; + rolling_digits[11] = roll_2[1]; + rolling_digits[12] = roll_2[2]; + rolling_digits[13] = roll_2[3]; + + rolling_digits[14] = roll_1[0]; + rolling_digits[15] = roll_1[1]; + rolling_digits[16] = roll_1[2]; + rolling_digits[17] = roll_1[3]; + + uint32_t rolling = 0; + for(int i = 0; i < 18; i++) { + rolling = (rolling * 3) + rolling_digits[i]; + } + // Max value = 2^28 (268435456) + if(rolling >= 0x10000000) { + FURI_LOG_E(TAG, "Rolling FAIL"); + instance->cnt = 0; + instance->btn = 0; + instance->serial = 0; + } else { + instance->cnt = subghz_protocol_blocks_reverse_key(rolling, 28); + instance->btn = fixed_1[0] >> 12; + instance->serial = fixed_1[0] << 20 | fixed_2[0]; + } + } else { + instance->cnt = 0; + instance->btn = 0; + instance->serial = 0; + } +} + +/** + * Security+ 2.0 half-message encoding + * @param roll_array[] roll_array part + * @param fixed[] fixed part + * @return return data + */ + +static uint64_t subghz_protocol_secplus_v2_encode_half(uint8_t roll_array[], uint32_t fixed) { + uint64_t data = 0; + uint16_t p[3] = {(fixed >> 10) & 0x3FF, fixed & 0x3FF, 0}; + uint8_t order = roll_array[0] << 2 | roll_array[1]; + uint8_t invert = roll_array[2] << 2 | roll_array[3]; + p[2] = (uint16_t)roll_array[4] << 8 | roll_array[5] << 6 | roll_array[6] << 4 | + roll_array[7] << 2 | roll_array[8]; + + if(!subghz_protocol_secplus_v2_mix_order_encode(order, p)) return 0; + if(!subghz_protocol_secplus_v2_mix_invet(invert, p)) return 0; + + for(int i = 0; i < 10; i++) { + data <<= 3; + data |= bit_read(p[0], 9 - i) << 2 | bit_read(p[1], 9 - i) << 1 | bit_read(p[2], 9 - i); + } + data |= ((uint64_t)order) << 34 | ((uint64_t)invert) << 30; + + return data; +} + +/** + * Security+ 2.0 message encoding + * @param instance SubGhzProtocolEncoderSecPlus_v2* + */ + +static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* instance) { + uint32_t fixed_1[1] = {instance->generic.btn << 12 | instance->generic.serial >> 20}; + uint32_t fixed_2[1] = {instance->generic.serial & 0xFFFFF}; + uint8_t rolling_digits[18] = {0}; + uint8_t roll_1[9] = {0}; + uint8_t roll_2[9] = {0}; + + instance->generic.cnt++; + //ToDo it is not known what value the counter starts + if(instance->generic.cnt > 0xFFFFFFF) instance->generic.cnt = 0xE500000; + uint32_t rolling = subghz_protocol_blocks_reverse_key(instance->generic.cnt, 28); + + for(int8_t i = 17; i > -1; i--) { + rolling_digits[i] = rolling % 3; + rolling /= 3; + } + + roll_2[8] = rolling_digits[0]; + roll_1[8] = rolling_digits[1]; + + roll_2[4] = rolling_digits[2]; + roll_2[5] = rolling_digits[3]; + roll_2[6] = rolling_digits[4]; + roll_2[7] = rolling_digits[5]; + + roll_1[4] = rolling_digits[6]; + roll_1[5] = rolling_digits[7]; + roll_1[6] = rolling_digits[8]; + roll_1[7] = rolling_digits[9]; + + roll_2[0] = rolling_digits[10]; + roll_2[1] = rolling_digits[11]; + roll_2[2] = rolling_digits[12]; + roll_2[3] = rolling_digits[13]; + + roll_1[0] = rolling_digits[14]; + roll_1[1] = rolling_digits[15]; + roll_1[2] = rolling_digits[16]; + roll_1[3] = rolling_digits[17]; + + instance->secplus_packet_1 = SECPLUS_V2_HEADER | SECPLUS_V2_PACKET_1 | + subghz_protocol_secplus_v2_encode_half(roll_1, fixed_1[0]); + instance->generic.data = SECPLUS_V2_HEADER | SECPLUS_V2_PACKET_2 | + subghz_protocol_secplus_v2_encode_half(roll_2, fixed_2[0]); +} + +static LevelDuration + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(ManchesterEncoderResult result) { + LevelDuration data = {.duration = 0, .level = 0}; + switch(result) { + case ManchesterEncoderResultShortLow: + data.duration = subghz_protocol_secplus_v2_const.te_short; + data.level = false; + break; + case ManchesterEncoderResultLongLow: + data.duration = subghz_protocol_secplus_v2_const.te_long; + data.level = false; + break; + case ManchesterEncoderResultLongHigh: + data.duration = subghz_protocol_secplus_v2_const.te_long; + data.level = true; + break; + case ManchesterEncoderResultShortHigh: + data.duration = subghz_protocol_secplus_v2_const.te_short; + data.level = true; + break; + + default: + furi_crash("SugGhz: ManchesterEncoderResult is incorrect."); + break; + } + return level_duration_make(data.level, data.duration); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + */ +static void + subghz_protocol_encoder_secplus_v2_get_upload(SubGhzProtocolEncoderSecPlus_v2* instance) { + furi_assert(instance); + size_t index = 0; + + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; + + //Send data packet 1 + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(!manchester_encoder_advance( + &enc_state, bit_read(instance->secplus_packet_1, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); + manchester_encoder_advance( + &enc_state, bit_read(instance->secplus_packet_1, i - 1), &result); + } + instance->encoder.upload[index++] = + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); + } + instance->encoder.upload[index] = subghz_protocol_encoder_secplus_v2_add_duration_to_upload( + manchester_encoder_finish(&enc_state)); + if(level_duration_get_level(instance->encoder.upload[index])) { + index++; + } + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v2_const.te_long * 136); + + //Send data packet 2 + manchester_encoder_reset(&enc_state); + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(!manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); + manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result); + } + instance->encoder.upload[index++] = + subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); + } + instance->encoder.upload[index] = subghz_protocol_encoder_secplus_v2_add_duration_to_upload( + manchester_encoder_finish(&enc_state)); + if(level_duration_get_level(instance->encoder.upload[index])) { + index++; + } + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_secplus_v2_const.te_long * 136); + + instance->encoder.size_upload = index; +} + +bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v2* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex( + flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Secplus_packet_1"); + break; + } + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->secplus_packet_1 = instance->secplus_packet_1 << 8 | key_data[i]; + } + + subghz_protocol_secplus_v2_remote_controller( + &instance->generic, instance->secplus_packet_1); + subghz_protocol_secplus_v2_encode(instance); + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_encoder_secplus_v2_get_upload(instance); + + //update data + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->secplus_packet_1 >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex( + flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Secplus_packet_1"); + break; + } + + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_secplus_v2_stop(void* context) { + SubGhzProtocolEncoderSecPlus_v2* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context) { + SubGhzProtocolEncoderSecPlus_v2* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +bool subghz_protocol_secplus_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolEncoderSecPlus_v2* instance = context; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->generic.btn = btn; + instance->generic.data_count_bit = + (uint8_t)subghz_protocol_secplus_v2_const.min_count_bit_for_found; + subghz_protocol_secplus_v2_encode(instance); + bool res = + subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->secplus_packet_1 >> i * 8) & 0xFF; + } + + if(res && + !flipper_format_write_hex(flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Secplus_packet_1"); + res = false; + } + return res; +} + void* subghz_protocol_decoder_secplus_v2_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolDecoderSecPlus_v2* instance = malloc(sizeof(SubGhzProtocolDecoderSecPlus_v2)); @@ -213,186 +747,6 @@ void subghz_protocol_decoder_secplus_v2_feed(void* context, bool level, uint32_t } } -/** - * Security+ 2.0 half-message decoding - * @param data data - * @param roll_array[] return roll_array part - * @param fixed[] return fixed part - * @return true On success - */ - -static bool - subghz_protocol_secplus_v2_decode_half(uint64_t data, uint8_t roll_array[], uint32_t* fixed) { - uint8_t order = (data >> 34) & 0x0f; - uint8_t invert = (data >> 30) & 0x0f; - uint16_t p[3] = {0}; - - for(int i = 29; i >= 0; i -= 3) { - p[0] = p[0] << 1 | bit_read(data, i); - p[1] = p[1] << 1 | bit_read(data, i - 1); - p[2] = p[2] << 1 | bit_read(data, i - 2); - } - - // selectively invert buffers - switch(invert) { - case 0x00: // 0b0000 (True, True, False), - p[0] = ~p[0] & 0x03FF; - p[1] = ~p[1] & 0x03FF; - break; - case 0x01: // 0b0001 (False, True, False), - p[1] = ~p[1] & 0x03FF; - break; - case 0x02: // 0b0010 (False, False, True), - p[2] = ~p[2] & 0x03FF; - break; - case 0x04: // 0b0100 (True, True, True), - p[0] = ~p[0] & 0x03FF; - p[1] = ~p[1] & 0x03FF; - p[2] = ~p[2] & 0x03FF; - break; - case 0x05: // 0b0101 (True, False, True), - case 0x0a: // 0b1010 (True, False, True), - p[0] = ~p[0] & 0x03FF; - p[2] = ~p[2] & 0x03FF; - break; - case 0x06: // 0b0110 (False, True, True), - p[1] = ~p[1] & 0x03FF; - p[2] = ~p[2] & 0x03FF; - break; - case 0x08: // 0b1000 (True, False, False), - p[0] = ~p[0] & 0x03FF; - break; - case 0x09: // 0b1001 (False, False, False), - break; - default: - FURI_LOG_E(TAG, "Invert FAIL"); - return false; - } - - uint16_t a = p[0], b = p[1], c = p[2]; - - // selectively reorder buffers - switch(order) { - case 0x06: // 0b0110 2, 1, 0], - case 0x09: // 0b1001 2, 1, 0], - p[2] = a; - p[1] = b; - p[0] = c; - break; - case 0x08: // 0b1000 1, 2, 0], - case 0x04: // 0b0100 1, 2, 0], - p[1] = a; - p[2] = b; - p[0] = c; - break; - case 0x01: // 0b0001 2, 0, 1], - p[2] = a; - p[0] = b; - p[1] = c; - break; - case 0x00: // 0b0000 0, 2, 1], - p[0] = a; - p[2] = b; - p[1] = c; - break; - case 0x05: // 0b0101 1, 0, 2], - p[1] = a; - p[0] = b; - p[2] = c; - break; - case 0x02: // 0b0010 0, 1, 2], - case 0x0A: // 0b1010 0, 1, 2], - p[0] = a; - p[1] = b; - p[2] = c; - break; - default: - FURI_LOG_E(TAG, "Order FAIL"); - return false; - } - - data = order << 4 | invert; - int k = 0; - for(int i = 6; i >= 0; i -= 2) { - roll_array[k++] = (data >> i) & 0x03; - if(roll_array[k] == 3) { - FURI_LOG_E(TAG, "Roll_Array FAIL"); - return false; - } - } - - for(int i = 8; i >= 0; i -= 2) { - roll_array[k++] = (p[2] >> i) & 0x03; - if(roll_array[k] == 3) { - FURI_LOG_E(TAG, "Roll_Array FAIL"); - return false; - } - } - - fixed[0] = p[0] << 10 | p[1]; - return true; -} - -/** - * Analysis of received data - * @param instance Pointer to a SubGhzBlockGeneric* instance - * @param packet_1 first part of the message - */ -static void - subghz_protocol_secplus_v2_remote_controller(SubGhzBlockGeneric* instance, uint64_t packet_1) { - uint32_t fixed_1[1]; - uint8_t roll_1[9] = {0}; - uint32_t fixed_2[1]; - uint8_t roll_2[9] = {0}; - uint8_t rolling_digits[18] = {0}; - - if(subghz_protocol_secplus_v2_decode_half(packet_1, roll_1, fixed_1) && - subghz_protocol_secplus_v2_decode_half(instance->data, roll_2, fixed_2)) { - rolling_digits[0] = roll_2[8]; - rolling_digits[1] = roll_1[8]; - - rolling_digits[2] = roll_2[4]; - rolling_digits[3] = roll_2[5]; - rolling_digits[4] = roll_2[6]; - rolling_digits[5] = roll_2[7]; - - rolling_digits[6] = roll_1[4]; - rolling_digits[7] = roll_1[5]; - rolling_digits[8] = roll_1[6]; - rolling_digits[9] = roll_1[7]; - - rolling_digits[10] = roll_2[0]; - rolling_digits[11] = roll_2[1]; - rolling_digits[12] = roll_2[2]; - rolling_digits[13] = roll_2[3]; - - rolling_digits[14] = roll_1[0]; - rolling_digits[15] = roll_1[1]; - rolling_digits[16] = roll_1[2]; - rolling_digits[17] = roll_1[3]; - - uint32_t rolling = 0; - for(int i = 0; i < 18; i++) { - rolling = (rolling * 3) + rolling_digits[i]; - } - // Max value = 2^28 (268435456) - if(rolling >= 0x10000000) { - FURI_LOG_E(TAG, "Rolling FAIL"); - instance->cnt = 0; - instance->btn = 0; - instance->serial = 0; - } else { - instance->cnt = subghz_protocol_blocks_reverse_key(rolling, 28); - instance->btn = fixed_1[0] >> 12; - instance->serial = fixed_1[0] << 20 | fixed_2[0]; - } - } else { - instance->cnt = 0; - instance->btn = 0; - instance->serial = 0; - } -} - uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v2* instance = context; diff --git a/lib/subghz/protocols/secplus_v2.h b/lib/subghz/protocols/secplus_v2.h index dbb1e1bd..3d695148 100644 --- a/lib/subghz/protocols/secplus_v2.h +++ b/lib/subghz/protocols/secplus_v2.h @@ -10,6 +10,61 @@ extern const SubGhzProtocolDecoder subghz_protocol_secplus_v2_decoder; extern const SubGhzProtocolEncoder subghz_protocol_secplus_v2_encoder; extern const SubGhzProtocol subghz_protocol_secplus_v2; +/** + * Allocate SubGhzProtocolEncoderSecPlus_v2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderSecPlus_v2* pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + */ +void* subghz_protocol_encoder_secplus_v2_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderSecPlus_v2. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + */ +void subghz_protocol_encoder_secplus_v2_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + */ +void subghz_protocol_encoder_secplus_v2_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 32 bit + * @param btn Button number, 8 bit + * @param cnt Container value, 28 bit + * @param manufacture_name Name of manufacturer's key + * @param frequency Transmission frequency, Hz + * @param preset Modulation, FuriHalSubGhzPreset + * @return true On success + */ +bool subghz_protocol_secplus_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + uint32_t frequency, + FuriHalSubGhzPreset preset); + /** * Allocate SubGhzProtocolDecoderSecPlus_v2. * @param environment Pointer to a SubGhzEnvironment instance From 69d90d5b005b02b21c751dce7f3bf93760552cf7 Mon Sep 17 00:00:00 2001 From: gornekich Date: Mon, 30 May 2022 18:39:43 +0300 Subject: [PATCH 23/28] nfc device: fix nfc cards save (#1277) --- applications/nfc/nfc_device.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/applications/nfc/nfc_device.c b/applications/nfc/nfc_device.c index ca3fc45a..51e14a6c 100644 --- a/applications/nfc/nfc_device.c +++ b/applications/nfc/nfc_device.c @@ -906,8 +906,11 @@ bool nfc_file_select(NfcDevice* dev) { furi_assert(dev); // Input events and views are managed by file_browser + string_t nfc_app_folder; + string_init_set_str(nfc_app_folder, NFC_APP_FOLDER); bool res = dialog_file_browser_show( - dev->dialogs, dev->load_path, dev->load_path, NFC_APP_EXTENSION, true, &I_Nfc_10px, true); + dev->dialogs, dev->load_path, nfc_app_folder, NFC_APP_EXTENSION, true, &I_Nfc_10px, true); + string_clear(nfc_app_folder); if(res) { string_t filename; string_init(filename); @@ -935,7 +938,7 @@ void nfc_device_clear(NfcDevice* dev) { nfc_device_data_clear(&dev->dev_data); memset(&dev->dev_data, 0, sizeof(dev->dev_data)); dev->format = NfcDeviceSaveFormatUid; - string_set_str(dev->load_path, NFC_APP_FOLDER); + string_reset(dev->load_path); } bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { From 66dbb689477516ee7067e46941225d09e3635bcf Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Tue, 31 May 2022 19:50:50 +0400 Subject: [PATCH 24/28] SubGhz: support 310 MHz and fix (#1262) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add frequency support 310 MHz * SubGhz: deleting a TMP file using the "Erase" button in Read RAW * SubGhz: fix frequency analyzer scan speed * SubGhz: fix start duration came_atomo protocol * SubGhz: refactoring subghz_setting * SubGhz: refactoring load setting frequency analyzer * SubGhz: fix load setting, default frequency * SubGhz: patch raw temp file remove code to work with string_t * Storage: parallel safe cli * SubGhz: new frequency settings loading system * Assets: recompile to include latest subghz custom frequency control changes Co-authored-by: あく --- applications/storage/storage_cli.c | 2 +- .../subghz_frequency_analyzer_worker.c | 19 +- .../subghz_frequency_analyzer_worker.h | 4 +- applications/subghz/helpers/subghz_testing.c | 3 +- .../subghz/scenes/subghz_scene_read_raw.c | 4 + applications/subghz/subghz_setting.c | 254 +++++++++--------- .../subghz/views/subghz_frequency_analyzer.c | 2 +- assets/resources/Manifest | 5 +- .../assets/setting_frequency_analyzer_user | 19 -- assets/resources/subghz/assets/setting_user | 38 ++- lib/subghz/protocols/came_atomo.c | 4 +- 11 files changed, 172 insertions(+), 182 deletions(-) delete mode 100644 assets/resources/subghz/assets/setting_frequency_analyzer_user diff --git a/applications/storage/storage_cli.c b/applications/storage/storage_cli.c index b35e37a8..4ce91770 100644 --- a/applications/storage/storage_cli.c +++ b/applications/storage/storage_cli.c @@ -593,7 +593,7 @@ static void storage_cli_factory_reset(Cli* cli, string_t args, void* context) { void storage_on_system_start() { #ifdef SRV_CLI Cli* cli = furi_record_open("cli"); - cli_add_command(cli, "storage", CliCommandFlagDefault, storage_cli, NULL); + cli_add_command(cli, "storage", CliCommandFlagParallelSafe, storage_cli, NULL); cli_add_command( cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL); furi_record_close("cli"); diff --git a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c index 88e2a621..69e59759 100644 --- a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -3,8 +3,6 @@ #include -#include "../subghz_i.h" - #define TAG "SubghzFrequencyAnalyzerWorker" #define SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD -95.0f @@ -82,7 +80,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { cc1101_flush_tx(&furi_hal_spi_bus_handle_subghz); cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_MDMCFG3, - 0b11111111); // symbol rate + 0b01111111); // symbol rate cc1101_write_reg( &furi_hal_spi_bus_handle_subghz, CC1101_AGCCTRL2, @@ -130,7 +128,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); // delay will be in range between 1 and 2ms - osDelay(2); + osDelay(3); rssi = furi_hal_subghz_get_rssi(); @@ -179,9 +177,12 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); // delay will be in range between 1 and 2ms - osDelay(2); + osDelay(3); rssi = furi_hal_subghz_get_rssi(); + + FURI_LOG_T(TAG, "#:%u:%f", frequency, (double)rssi); + if(frequency_rssi.rssi < rssi) { frequency_rssi.rssi = rssi; frequency_rssi.frequency = frequency; @@ -222,7 +223,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { return 0; } -SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc() { +SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(void* context) { + furi_assert(context); SubGhzFrequencyAnalyzerWorker* instance = malloc(sizeof(SubGhzFrequencyAnalyzerWorker)); instance->thread = furi_thread_alloc(); @@ -231,8 +233,8 @@ SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc() { furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, subghz_frequency_analyzer_worker_thread); - instance->setting = subghz_setting_alloc(); - subghz_setting_load(instance->setting, "/ext/subghz/assets/setting_frequency_analyzer_user"); + SubGhz* subghz = context; + instance->setting = subghz->setting; return instance; } @@ -240,7 +242,6 @@ void subghz_frequency_analyzer_worker_free(SubGhzFrequencyAnalyzerWorker* instan furi_assert(instance); furi_thread_free(instance->thread); - subghz_setting_free(instance->setting); free(instance); } diff --git a/applications/subghz/helpers/subghz_frequency_analyzer_worker.h b/applications/subghz/helpers/subghz_frequency_analyzer_worker.h index 93f7caf0..424270a0 100644 --- a/applications/subghz/helpers/subghz_frequency_analyzer_worker.h +++ b/applications/subghz/helpers/subghz_frequency_analyzer_worker.h @@ -1,6 +1,7 @@ #pragma once #include +#include "../subghz_i.h" typedef struct SubGhzFrequencyAnalyzerWorker SubGhzFrequencyAnalyzerWorker; @@ -14,9 +15,10 @@ typedef struct { /** Allocate SubGhzFrequencyAnalyzerWorker * + * @param context SubGhz* context * @return SubGhzFrequencyAnalyzerWorker* */ -SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(); +SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(void* context); /** Free SubGhzFrequencyAnalyzerWorker * diff --git a/applications/subghz/helpers/subghz_testing.c b/applications/subghz/helpers/subghz_testing.c index f4cbe4a4..8afa868e 100644 --- a/applications/subghz/helpers/subghz_testing.c +++ b/applications/subghz/helpers/subghz_testing.c @@ -4,6 +4,7 @@ const uint32_t subghz_frequencies_testing[] = { /* 300 - 348 */ 300000000, 304500000, + 310000000, 312025000, 313250000, 313625000, @@ -34,4 +35,4 @@ const uint32_t subghz_frequencies_testing[] = { const uint32_t subghz_frequencies_count_testing = sizeof(subghz_frequencies_testing) / sizeof(uint32_t); -const uint32_t subghz_frequencies_433_92_testing = 12; +const uint32_t subghz_frequencies_433_92_testing = 13; diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/subghz/scenes/subghz_scene_read_raw.c index 191a7285..38a9067c 100644 --- a/applications/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/subghz/scenes/subghz_scene_read_raw.c @@ -168,6 +168,10 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { case SubGhzCustomEventViewReadRAWErase: subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; + if(subghz_scene_read_raw_update_filename(subghz)) { + string_set(subghz->file_path_tmp, subghz->file_path); + subghz_delete_file(subghz); + } notification_message(subghz->notifications, &sequence_reset_rgb); return true; break; diff --git a/applications/subghz/subghz_setting.c b/applications/subghz/subghz_setting.c index 4d40d49e..9dcfb291 100644 --- a/applications/subghz/subghz_setting.c +++ b/applications/subghz/subghz_setting.c @@ -7,20 +7,19 @@ #define TAG "SubGhzSetting" -#define SUBGHZ_SETTING_FILE_VERSION 1 #define SUBGHZ_SETTING_FILE_TYPE "Flipper SubGhz Setting File" +#define SUBGHZ_SETTING_FILE_VERSION 1 -typedef enum { - SubGhzSettingStateNoLoad = 0, - SubGhzSettingStateLoadFrequencyDefault, - SubGhzSettingStateOkLoad, -} SubGhzSettingState; +#define FREQUENCY_FLAG_DEFAULT (1 << 31) +#define FREQUENCY_MASK (0xFFFFFFFF ^ FREQUENCY_FLAG_DEFAULT) -static const uint32_t subghz_frequencies[] = { +/* Default */ +static const uint32_t subghz_frequency_list[] = { /* 300 - 348 */ 300000000, 303875000, 304250000, + 310000000, 315000000, 318000000, @@ -29,7 +28,7 @@ static const uint32_t subghz_frequencies[] = { 418000000, 433075000, /* LPD433 first */ 433420000, - 433920000, /* LPD433 mid */ + 433920000 | FREQUENCY_FLAG_DEFAULT, /* LPD433 mid */ 434420000, 434775000, /* LPD433 last channels */ 438900000, @@ -40,7 +39,9 @@ static const uint32_t subghz_frequencies[] = { 925000000, 0, }; -static const uint32_t subghz_hopper_frequencies[] = { + +static const uint32_t subghz_hopper_frequency_list[] = { + 310000000, 315000000, 318000000, 390000000, @@ -48,13 +49,14 @@ static const uint32_t subghz_hopper_frequencies[] = { 868350000, 0, }; -static const uint32_t subghz_frequency_default_index = 9; -static const uint32_t subghz_frequencies_region_eu_ru[] = { +/* Europe and Russia */ +static const uint32_t subghz_frequency_list_region_eu_ru[] = { /* 300 - 348 */ 300000000, 303875000, 304250000, + 310000000, 315000000, 318000000, @@ -63,7 +65,7 @@ static const uint32_t subghz_frequencies_region_eu_ru[] = { 418000000, 433075000, /* LPD433 first */ 433420000, - 433920000, /* LPD433 mid */ + 433920000 | FREQUENCY_FLAG_DEFAULT, /* LPD433 mid */ 434420000, 434775000, /* LPD433 last channels */ 438900000, @@ -74,7 +76,8 @@ static const uint32_t subghz_frequencies_region_eu_ru[] = { 925000000, 0, }; -static const uint32_t subghz_hopper_frequencies_region_eu_ru[] = { +static const uint32_t subghz_hopper_frequency_list_region_eu_ru[] = { + 310000000, 315000000, 318000000, 390000000, @@ -82,13 +85,14 @@ static const uint32_t subghz_hopper_frequencies_region_eu_ru[] = { 868350000, 0, }; -static const uint32_t subghz_frequency_default_index_region_eu_ru = 9; -static const uint32_t subghz_frequencies_region_us_ca_au[] = { +/* Region 0 */ +static const uint32_t subghz_frequency_list_region_us_ca_au[] = { /* 300 - 348 */ 300000000, 303875000, 304250000, + 310000000, 315000000, 318000000, @@ -97,7 +101,7 @@ static const uint32_t subghz_frequencies_region_us_ca_au[] = { 418000000, 433075000, /* LPD433 first */ 433420000, - 433920000, /* LPD433 mid */ + 433920000 | FREQUENCY_FLAG_DEFAULT, /* LPD433 mid */ 434420000, 434775000, /* LPD433 last channels */ 438900000, @@ -108,7 +112,8 @@ static const uint32_t subghz_frequencies_region_us_ca_au[] = { 925000000, 0, }; -static const uint32_t subghz_hopper_frequencies_region_us_ca_au[] = { +static const uint32_t subghz_hopper_frequency_list_region_us_ca_au[] = { + 310000000, 315000000, 318000000, 390000000, @@ -116,13 +121,13 @@ static const uint32_t subghz_hopper_frequencies_region_us_ca_au[] = { 868350000, 0, }; -static const uint32_t subghz_frequency_default_index_region_us_ca_au = 9; -static const uint32_t subghz_frequencies_region_jp[] = { +static const uint32_t subghz_frequency_list_region_jp[] = { /* 300 - 348 */ 300000000, 303875000, 304250000, + 310000000, 315000000, 318000000, @@ -131,7 +136,7 @@ static const uint32_t subghz_frequencies_region_jp[] = { 418000000, 433075000, /* LPD433 first */ 433420000, - 433920000, /* LPD433 mid */ + 433920000 | FREQUENCY_FLAG_DEFAULT, /* LPD433 mid */ 434420000, 434775000, /* LPD433 last channels */ 438900000, @@ -142,7 +147,8 @@ static const uint32_t subghz_frequencies_region_jp[] = { 925000000, 0, }; -static const uint32_t subghz_hopper_frequencies_region_jp[] = { +static const uint32_t subghz_hopper_frequency_list_region_jp[] = { + 310000000, 315000000, 318000000, 390000000, @@ -150,72 +156,88 @@ static const uint32_t subghz_hopper_frequencies_region_jp[] = { 868350000, 0, }; -static const uint32_t subghz_frequency_default_index_region_jp = 9; -LIST_DEF(FrequenciesList, uint32_t) +LIST_DEF(FrequencyList, uint32_t) + +#define M_OPL_FrequencyList_t() LIST_OPLIST(FrequencyList) struct SubGhzSetting { - FrequenciesList_t frequencies; - FrequenciesList_t hopper_frequencies; - size_t frequencies_count; - size_t hopper_frequencies_count; - uint32_t frequency_default_index; + FrequencyList_t frequencies; + FrequencyList_t hopper_frequencies; }; SubGhzSetting* subghz_setting_alloc(void) { SubGhzSetting* instance = malloc(sizeof(SubGhzSetting)); - FrequenciesList_init(instance->frequencies); - FrequenciesList_init(instance->hopper_frequencies); + FrequencyList_init(instance->frequencies); + FrequencyList_init(instance->hopper_frequencies); return instance; } void subghz_setting_free(SubGhzSetting* instance) { furi_assert(instance); - FrequenciesList_clear(instance->frequencies); - FrequenciesList_clear(instance->hopper_frequencies); + FrequencyList_clear(instance->frequencies); + FrequencyList_clear(instance->hopper_frequencies); free(instance); } -void subghz_setting_load_default( +static void subghz_setting_load_default_region( SubGhzSetting* instance, const uint32_t frequencies[], - const uint32_t hopper_frequencies[], - const uint32_t frequency_default_index) { + const uint32_t hopper_frequencies[]) { furi_assert(instance); - size_t i = 0; - FrequenciesList_clear(instance->frequencies); - FrequenciesList_clear(instance->hopper_frequencies); - i = 0; - while(frequencies[i]) { - FrequenciesList_push_back(instance->frequencies, frequencies[i]); - i++; - } - instance->frequencies_count = i; - i = 0; - while(hopper_frequencies[i]) { - FrequenciesList_push_back(instance->hopper_frequencies, hopper_frequencies[i]); - i++; - } - instance->hopper_frequencies_count = i; + FrequencyList_reset(instance->frequencies); + FrequencyList_reset(instance->hopper_frequencies); - instance->frequency_default_index = frequency_default_index; + while(*frequencies) { + FrequencyList_push_back(instance->frequencies, *frequencies); + frequencies++; + } + + while(*hopper_frequencies) { + FrequencyList_push_back(instance->hopper_frequencies, *hopper_frequencies); + hopper_frequencies++; + } +} + +void subghz_setting_load_default(SubGhzSetting* instance) { + switch(furi_hal_version_get_hw_region()) { + case FuriHalVersionRegionEuRu: + subghz_setting_load_default_region( + instance, + subghz_frequency_list_region_eu_ru, + subghz_hopper_frequency_list_region_eu_ru); + break; + case FuriHalVersionRegionUsCaAu: + subghz_setting_load_default_region( + instance, + subghz_frequency_list_region_us_ca_au, + subghz_hopper_frequency_list_region_us_ca_au); + break; + case FuriHalVersionRegionJp: + subghz_setting_load_default_region( + instance, subghz_frequency_list_region_jp, subghz_hopper_frequency_list_region_jp); + break; + + default: + subghz_setting_load_default_region( + instance, subghz_frequency_list, subghz_hopper_frequency_list); + break; + } } void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { furi_assert(instance); - FrequenciesList_clear(instance->frequencies); - FrequenciesList_clear(instance->hopper_frequencies); - Storage* storage = furi_record_open("storage"); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); string_t temp_str; string_init(temp_str); uint32_t temp_data32; - SubGhzSettingState loading = SubGhzSettingStateNoLoad; - uint16_t i = 0; + bool temp_bool; + + subghz_setting_load_default(instance); if(file_path) { do { @@ -236,63 +258,60 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { break; } + // Standard frequencies (optional) + temp_bool = true; + flipper_format_read_bool(fff_data_file, "add_standard_frequencies", &temp_bool, 1); + if(!temp_bool) { + FURI_LOG_I(TAG, "Removing standard frequencies"); + FrequencyList_reset(instance->frequencies); + FrequencyList_reset(instance->hopper_frequencies); + } else { + FURI_LOG_I(TAG, "Keeping standard frequencies"); + } + + // Load frequencies if(!flipper_format_rewind(fff_data_file)) { FURI_LOG_E(TAG, "Rewind error"); break; } - i = 0; while(flipper_format_read_uint32( - fff_data_file, "Frequency", (uint32_t*)&temp_data32, 1)) { + fff_data_file, "frequency", (uint32_t*)&temp_data32, 1)) { if(furi_hal_subghz_is_frequency_valid(temp_data32)) { FURI_LOG_I(TAG, "Frequency loaded %lu", temp_data32); - FrequenciesList_push_back(instance->frequencies, temp_data32); - i++; + FrequencyList_push_back(instance->frequencies, temp_data32); } else { FURI_LOG_E(TAG, "Frequency not supported %lu", temp_data32); } } - instance->frequencies_count = i; + // Load hopper frequencies if(!flipper_format_rewind(fff_data_file)) { FURI_LOG_E(TAG, "Rewind error"); break; } - i = 0; while(flipper_format_read_uint32( - fff_data_file, "Hopper_frequency", (uint32_t*)&temp_data32, 1)) { + fff_data_file, "hopper_frequency", (uint32_t*)&temp_data32, 1)) { if(furi_hal_subghz_is_frequency_valid(temp_data32)) { FURI_LOG_I(TAG, "Hopper frequency loaded %lu", temp_data32); - FrequenciesList_push_back(instance->hopper_frequencies, temp_data32); - i++; + FrequencyList_push_back(instance->hopper_frequencies, temp_data32); } else { FURI_LOG_E(TAG, "Hopper frequency not supported %lu", temp_data32); } } - instance->hopper_frequencies_count = i; + // Default frequency (optional) if(!flipper_format_rewind(fff_data_file)) { FURI_LOG_E(TAG, "Rewind error"); break; } - if(!flipper_format_read_uint32( - fff_data_file, "Frequency_default", (uint32_t*)&temp_data32, 1)) { - FURI_LOG_E(TAG, "Frequency default missing"); - break; - } - - for(i = 0; i < instance->frequencies_count; i++) { - if(subghz_setting_get_frequency(instance, i) == temp_data32) { - instance->frequency_default_index = i; - FURI_LOG_I(TAG, "Frequency default index %lu", i); - loading = SubGhzSettingStateLoadFrequencyDefault; - break; - } - } - - if(loading == SubGhzSettingStateLoadFrequencyDefault) { - loading = SubGhzSettingStateOkLoad; - } else { - FURI_LOG_E(TAG, "Frequency default index missing"); + if(flipper_format_read_uint32(fff_data_file, "default_frequency", &temp_data32, 1)) { + for + M_EACH(frequency, instance->frequencies, FrequencyList_t) { + *frequency &= FREQUENCY_MASK; + if(*frequency == temp_data32) { + *frequency |= FREQUENCY_FLAG_DEFAULT; + } + } } } while(false); } @@ -301,67 +320,56 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { flipper_format_free(fff_data_file); furi_record_close("storage"); - if(loading != SubGhzSettingStateOkLoad) { - switch(furi_hal_version_get_hw_region()) { - case FuriHalVersionRegionEuRu: - subghz_setting_load_default( - instance, - subghz_frequencies_region_eu_ru, - subghz_hopper_frequencies_region_eu_ru, - subghz_frequency_default_index_region_eu_ru); - break; - case FuriHalVersionRegionUsCaAu: - subghz_setting_load_default( - instance, - subghz_frequencies_region_us_ca_au, - subghz_hopper_frequencies_region_us_ca_au, - subghz_frequency_default_index_region_us_ca_au); - break; - case FuriHalVersionRegionJp: - subghz_setting_load_default( - instance, - subghz_frequencies_region_jp, - subghz_hopper_frequencies_region_jp, - subghz_frequency_default_index_region_jp); - break; - - default: - subghz_setting_load_default( - instance, - subghz_frequencies, - subghz_hopper_frequencies, - subghz_frequency_default_index); - break; - } + if(!FrequencyList_size(instance->frequencies) || + !FrequencyList_size(instance->hopper_frequencies)) { + FURI_LOG_E(TAG, "Error loading user settings, loading default settings"); + subghz_setting_load_default(instance); } } size_t subghz_setting_get_frequency_count(SubGhzSetting* instance) { furi_assert(instance); - return instance->frequencies_count; + return FrequencyList_size(instance->frequencies); } size_t subghz_setting_get_hopper_frequency_count(SubGhzSetting* instance) { furi_assert(instance); - return instance->hopper_frequencies_count; + return FrequencyList_size(instance->hopper_frequencies); } uint32_t subghz_setting_get_frequency(SubGhzSetting* instance, size_t idx) { furi_assert(instance); - return *FrequenciesList_get(instance->frequencies, idx); + uint32_t* ret = FrequencyList_get(instance->frequencies, idx); + if(ret) { + return (*ret) & FREQUENCY_MASK; + } else { + return 0; + } } uint32_t subghz_setting_get_hopper_frequency(SubGhzSetting* instance, size_t idx) { furi_assert(instance); - return *FrequenciesList_get(instance->hopper_frequencies, idx); + uint32_t* ret = FrequencyList_get(instance->hopper_frequencies, idx); + if(ret) { + return *ret; + } else { + return 0; + } } uint32_t subghz_setting_get_frequency_default_index(SubGhzSetting* instance) { furi_assert(instance); - return instance->frequency_default_index; + for(size_t i = 0; i < FrequencyList_size(instance->frequencies); i++) { + uint32_t frequency = *FrequencyList_get(instance->frequencies, i); + if(frequency & FREQUENCY_FLAG_DEFAULT) { + return i; + } + } + return 0; } uint32_t subghz_setting_get_default_frequency(SubGhzSetting* instance) { furi_assert(instance); - return *FrequenciesList_get(instance->frequencies, instance->frequency_default_index); + return subghz_setting_get_frequency( + instance, subghz_setting_get_frequency_default_index(instance)); } diff --git a/applications/subghz/views/subghz_frequency_analyzer.c b/applications/subghz/views/subghz_frequency_analyzer.c index ec3c5ee9..d3f77315 100644 --- a/applications/subghz/views/subghz_frequency_analyzer.c +++ b/applications/subghz/views/subghz_frequency_analyzer.c @@ -111,7 +111,7 @@ void subghz_frequency_analyzer_enter(void* context) { SubGhzFrequencyAnalyzer* instance = context; //Start worker - instance->worker = subghz_frequency_analyzer_worker_alloc(); + instance->worker = subghz_frequency_analyzer_worker_alloc(instance->context); subghz_frequency_analyzer_worker_set_pair_callback( instance->worker, diff --git a/assets/resources/Manifest b/assets/resources/Manifest index 76b90232..9d854b13 100644 --- a/assets/resources/Manifest +++ b/assets/resources/Manifest @@ -1,5 +1,5 @@ V:0 -T:1653334495 +T:1654009290 D:badusb D:dolphin D:infrared @@ -235,8 +235,7 @@ F:dda1ef895b8a25fde57c874feaaef997:650:subghz/assets/came_atomo F:610a0ffa2479a874f2060eb2348104c5:2712:subghz/assets/keeloq_mfcodes F:9214f9c10463b746a27e82ce0b96e040:465:subghz/assets/keeloq_mfcodes_user F:653bd8d349055a41e1152e557d4a52d3:202:subghz/assets/nice_flor_s -F:00e967e5c558e44a0651bb821d5cf1d0:414:subghz/assets/setting_frequency_analyzer_user -F:16e8c7cb4a13f26ea55b2b0a59f9cc7a:554:subghz/assets/setting_user +F:c6ec4374275cd20f482ecd46de9f53e3:528:subghz/assets/setting_user D:u2f/assets F:7e11e688e39034bbb9d88410044795e1:365:u2f/assets/cert.der F:f60b88c20ed479ed9684e249f7134618:264:u2f/assets/cert_key.u2f diff --git a/assets/resources/subghz/assets/setting_frequency_analyzer_user b/assets/resources/subghz/assets/setting_frequency_analyzer_user deleted file mode 100644 index 2c03a402..00000000 --- a/assets/resources/subghz/assets/setting_frequency_analyzer_user +++ /dev/null @@ -1,19 +0,0 @@ -Filetype: Flipper SubGhz Setting File -Version: 1 -Frequency_default: 433920000 -Frequency: 300000000 -Frequency: 303875000 -Frequency: 304250000 -Frequency: 315000000 -Frequency: 318000000 -Frequency: 390000000 -Frequency: 418000000 -Frequency: 433075000 -Frequency: 433420000 -Frequency: 433920000 -Frequency: 434420000 -Frequency: 434775000 -Frequency: 438900000 -Frequency: 868350000 -Frequency: 915000000 -Frequency: 925000000 diff --git a/assets/resources/subghz/assets/setting_user b/assets/resources/subghz/assets/setting_user index 11bd984d..413dbf31 100644 --- a/assets/resources/subghz/assets/setting_user +++ b/assets/resources/subghz/assets/setting_user @@ -1,24 +1,18 @@ Filetype: Flipper SubGhz Setting File Version: 1 -Frequency_default: 433920000 -Frequency: 300000000 -Frequency: 303875000 -Frequency: 304250000 -Frequency: 315000000 -Frequency: 318000000 -Frequency: 390000000 -Frequency: 418000000 -Frequency: 433075000 -Frequency: 433420000 -Frequency: 433920000 -Frequency: 434420000 -Frequency: 434775000 -Frequency: 438900000 -Frequency: 868350000 -Frequency: 915000000 -Frequency: 925000000 -Hopper_frequency: 315000000 -Hopper_frequency: 318000000 -Hopper_frequency: 390000000 -Hopper_frequency: 433920000 -Hopper_frequency: 868350000 + +# Add Standard frequencies for your region +#add_standard_frequencies: true + +# Default Frequency: used as default for "Read" and "Read Raw" +#default_frequency: 433920000 + +# Frequencies used for "Read", "Read Raw" and "Frequency Analyzer" +#frequency: 300000000 +#frequency: 310000000 +#frequency: 320000000 + +# Frequencies used for hopping mode (keep this list small or flipper will miss signal) +#hopper_frequency: 300000000 +#hopper_frequency: 310000000 +#hopper_frequency: 310000000 diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 51e7fe0b..ad98f83f 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -108,8 +108,8 @@ void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t ManchesterEvent event = ManchesterEventReset; switch(instance->decoder.parser_step) { case CameAtomoDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 65) < - subghz_protocol_came_atomo_const.te_delta * 20)) { + if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 60) < + subghz_protocol_came_atomo_const.te_delta * 40)) { //Found header CAME instance->decoder.parser_step = CameAtomoDecoderStepDecoderData; instance->decoder.decode_data = 0; From 2c4b2b87753899f988ab4cbca11480061c966956 Mon Sep 17 00:00:00 2001 From: UberGuidoZ <57457139+UberGuidoZ@users.noreply.github.com> Date: Tue, 31 May 2022 15:35:31 -0700 Subject: [PATCH 25/28] Fixing typo across many files (#1287) * Update furi_hal_subghz.c * Update subghz_scene_read_raw.c * Update subghz_i.c * Update came_twee.c * Update secplus_v2.c --- applications/subghz/scenes/subghz_scene_read_raw.c | 2 +- applications/subghz/subghz_i.c | 6 +++--- firmware/targets/f7/furi_hal/furi_hal_subghz.c | 4 ++-- lib/subghz/protocols/came_twee.c | 2 +- lib/subghz/protocols/secplus_v2.c | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/subghz/scenes/subghz_scene_read_raw.c index 38a9067c..ae3abd6a 100644 --- a/applications/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/subghz/scenes/subghz_scene_read_raw.c @@ -184,7 +184,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW); return true; } else { - furi_crash("SugGhz: RAW file name update error."); + furi_crash("SubGhz: RAW file name update error."); } break; diff --git a/applications/subghz/subghz_i.c b/applications/subghz/subghz_i.c index 5439fd2a..33ceb9bb 100644 --- a/applications/subghz/subghz_i.c +++ b/applications/subghz/subghz_i.c @@ -54,7 +54,7 @@ void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_ subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev476Async) { string_set_str(modulation, "FM"); } else { - furi_crash("SugGhz: Modulation is incorrect."); + furi_crash("SubGhz: Modulation is incorrect."); } } } @@ -71,7 +71,7 @@ void subghz_begin(SubGhz* subghz, FuriHalSubGhzPreset preset) { uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) { furi_assert(subghz); if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SugGhz: Incorrect RX frequency."); + furi_crash("SubGhz: Incorrect RX frequency."); } furi_assert( subghz->txrx->txrx_state != SubGhzTxRxStateRx && @@ -92,7 +92,7 @@ uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) { static bool subghz_tx(SubGhz* subghz, uint32_t frequency) { furi_assert(subghz); if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SugGhz: Incorrect TX frequency."); + furi_crash("SubGhz: Incorrect TX frequency."); } furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep); furi_hal_subghz_idle(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 7f40a152..79262ef5 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -412,7 +412,7 @@ void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset) { furi_hal_subghz_load_registers(furi_hal_subghz_preset_gfsk_9_99kb_async_regs); furi_hal_subghz_load_patable(furi_hal_subghz_preset_gfsk_async_patable); } else { - furi_crash("SugGhz: Missing config."); + furi_crash("SubGhz: Missing config."); } furi_hal_subghz_preset = preset; } @@ -564,7 +564,7 @@ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value) { } else if(value >= 778999847 && value <= 928000000) { furi_hal_subghz_set_path(FuriHalSubGhzPath868); } else { - furi_crash("SugGhz: Incorrect frequency during set."); + furi_crash("SubGhz: Incorrect frequency during set."); } return value; } diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index e5fb12d1..1b60e111 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -145,7 +145,7 @@ static LevelDuration break; default: - furi_crash("SugGhz: ManchesterEncoderResult is incorrect."); + furi_crash("SubGhz: ManchesterEncoderResult is incorrect."); break; } return level_duration_make(data.level, data.duration); diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index af4de934..70ff19e4 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -442,7 +442,7 @@ static LevelDuration break; default: - furi_crash("SugGhz: ManchesterEncoderResult is incorrect."); + furi_crash("SubGhz: ManchesterEncoderResult is incorrect."); break; } return level_duration_make(data.level, data.duration); From b625e84424f2058b0d507d2959600d0ff8b7ddad Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 1 Jun 2022 16:17:21 +0400 Subject: [PATCH 26/28] SubGhz: Add Chamberlain 7-Code, Chamberlain 8-Code, Chamberlain 9-Code (#1288) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: fix protocol MegaCode start duration * SubGhz: add reception Chamberlain Code 7, 8, 9 protocols * SubGhz: Generating an upload from HEX data and a duration of 1 bit * SubGhz: add transmit Chamberlain Code 7, 8, 9 protocol * SubGhz: Rename Firefly -> Linear Co-authored-by: あく --- .../subghz/scenes/subghz_scene_set_type.c | 10 +- applications/unit_tests/subghz/subghz_test.c | 16 +- .../subghz/{firely.sub => linear.sub} | 2 +- .../{firefly_raw.sub => linear_raw.sub} | 0 lib/subghz/blocks/encoder.c | 42 ++ lib/subghz/blocks/encoder.h | 36 ++ lib/subghz/protocols/chamberlain_code.c | 483 ++++++++++++++++++ lib/subghz/protocols/chamberlain_code.h | 109 ++++ lib/subghz/protocols/firefly.h | 109 ---- lib/subghz/protocols/{firefly.c => linear.c} | 198 ++++--- lib/subghz/protocols/linear.h | 109 ++++ lib/subghz/protocols/megacode.c | 2 +- lib/subghz/protocols/registry.c | 3 +- lib/subghz/protocols/registry.h | 3 +- 14 files changed, 896 insertions(+), 226 deletions(-) rename assets/unit_tests/subghz/{firely.sub => linear.sub} (88%) rename assets/unit_tests/subghz/{firefly_raw.sub => linear_raw.sub} (100%) create mode 100644 lib/subghz/protocols/chamberlain_code.c create mode 100644 lib/subghz/protocols/chamberlain_code.h delete mode 100644 lib/subghz/protocols/firefly.h rename lib/subghz/protocols/{firefly.c => linear.c} (55%) create mode 100644 lib/subghz/protocols/linear.h diff --git a/applications/subghz/scenes/subghz_scene_set_type.c b/applications/subghz/scenes/subghz_scene_set_type.c index b64d0023..a6f0c3ea 100644 --- a/applications/subghz/scenes/subghz_scene_set_type.c +++ b/applications/subghz/scenes/subghz_scene_set_type.c @@ -22,7 +22,7 @@ enum SubmenuIndex { SubmenuIndexGateTX, SubmenuIndexDoorHan_315_00, SubmenuIndexDoorHan_433_92, - SubmenuIndexFirefly_300_00, + SubmenuIndexLinear_300_00, SubmenuIndexLiftMaster_315_00, SubmenuIndexLiftMaster_390_00, SubmenuIndexSecPlus_v2_310_00, @@ -117,8 +117,8 @@ void subghz_scene_set_type_on_enter(void* context) { subghz); submenu_add_item( subghz->submenu, - "Firefly_300", - SubmenuIndexFirefly_300_00, + "Linear_300", + SubmenuIndexLinear_300_00, subghz_scene_set_type_submenu_callback, subghz); submenu_add_item( @@ -256,11 +256,11 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { generated_protocol = true; } break; - case SubmenuIndexFirefly_300_00: + case SubmenuIndexLinear_300_00: key = (key & 0x3FF); if(subghz_scene_set_type_submenu_gen_data_protocol( subghz, - SUBGHZ_PROTOCOL_FIREFLY_NAME, + SUBGHZ_PROTOCOL_LINEAR_NAME, key, 10, 300000000, diff --git a/applications/unit_tests/subghz/subghz_test.c b/applications/unit_tests/subghz/subghz_test.c index 9e5b6599..c8f23780 100644 --- a/applications/unit_tests/subghz/subghz_test.c +++ b/applications/unit_tests/subghz/subghz_test.c @@ -328,10 +328,10 @@ MU_TEST(subghz_decoder_star_line_test) { "Test decoder " SUBGHZ_PROTOCOL_STAR_LINE_NAME " error\r\n"); } -MU_TEST(subghz_decoder_firefly_test) { +MU_TEST(subghz_decoder_linear_test) { mu_assert( - subghz_decoder_test("/ext/unit_tests/subghz/firefly_raw.sub", SUBGHZ_PROTOCOL_FIREFLY_NAME), - "Test decoder " SUBGHZ_PROTOCOL_FIREFLY_NAME " error\r\n"); + subghz_decoder_test("/ext/unit_tests/subghz/linear_raw.sub", SUBGHZ_PROTOCOL_LINEAR_NAME), + "Test decoder " SUBGHZ_PROTOCOL_LINEAR_NAME " error\r\n"); } MU_TEST(subghz_decoder_megacode_test) { @@ -398,10 +398,10 @@ MU_TEST(subghz_encoder_keelog_test) { "Test encoder " SUBGHZ_PROTOCOL_KEELOQ_NAME " error\r\n"); } -MU_TEST(subghz_encoder_firefly_test) { +MU_TEST(subghz_encoder_linear_test) { mu_assert( - subghz_encoder_test("/ext/unit_tests/subghz/firely.sub"), - "Test encoder " SUBGHZ_PROTOCOL_FIREFLY_NAME " error\r\n"); + subghz_encoder_test("/ext/unit_tests/subghz/linear.sub"), + "Test encoder " SUBGHZ_PROTOCOL_LINEAR_NAME " error\r\n"); } MU_TEST(subghz_encoder_megacode_test) { @@ -454,7 +454,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_somfy_keytis_test); MU_RUN_TEST(subghz_decoder_somfy_telis_test); MU_RUN_TEST(subghz_decoder_star_line_test); - MU_RUN_TEST(subghz_decoder_firefly_test); + MU_RUN_TEST(subghz_decoder_linear_test); MU_RUN_TEST(subghz_decoder_megacode_test); MU_RUN_TEST(subghz_decoder_secplus_v1_test); MU_RUN_TEST(subghz_decoder_secplus_v2_test); @@ -466,7 +466,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_gate_tx_test); MU_RUN_TEST(subghz_encoder_nice_flo_test); MU_RUN_TEST(subghz_encoder_keelog_test); - MU_RUN_TEST(subghz_encoder_firefly_test); + MU_RUN_TEST(subghz_encoder_linear_test); MU_RUN_TEST(subghz_encoder_megacode_test); MU_RUN_TEST(subghz_encoder_holtek_test); MU_RUN_TEST(subghz_encoder_secplus_v1_test); diff --git a/assets/unit_tests/subghz/firely.sub b/assets/unit_tests/subghz/linear.sub similarity index 88% rename from assets/unit_tests/subghz/firely.sub rename to assets/unit_tests/subghz/linear.sub index a38c21df..19ab5d72 100644 --- a/assets/unit_tests/subghz/firely.sub +++ b/assets/unit_tests/subghz/linear.sub @@ -2,6 +2,6 @@ Filetype: Flipper SubGhz Key File Version: 1 Frequency: 300000000 Preset: FuriHalSubGhzPresetOok650Async -Protocol: Firefly +Protocol: Linear Bit: 10 Key: 00 00 00 00 00 00 01 E4 diff --git a/assets/unit_tests/subghz/firefly_raw.sub b/assets/unit_tests/subghz/linear_raw.sub similarity index 100% rename from assets/unit_tests/subghz/firefly_raw.sub rename to assets/unit_tests/subghz/linear_raw.sub diff --git a/lib/subghz/blocks/encoder.c b/lib/subghz/blocks/encoder.c index e4b1ddba..3cefc2c1 100644 --- a/lib/subghz/blocks/encoder.c +++ b/lib/subghz/blocks/encoder.c @@ -1,3 +1,45 @@ #include "encoder.h" +#include "math.h" +#include #define TAG "SubGhzBlockEncoder" + +void subghz_protocol_blocks_set_bit_array( + bool bit_value, + uint8_t data_array[], + size_t set_index_bit, + size_t max_size_array) { + furi_assert(set_index_bit < max_size_array * 8); + bit_write(data_array[set_index_bit >> 3], 7 - (set_index_bit & 0x7), bit_value); +} + +bool subghz_protocol_blocks_get_bit_array(uint8_t data_array[], size_t read_index_bit) { + return bit_read(data_array[read_index_bit >> 3], 7 - (read_index_bit & 0x7)); +} + +size_t subghz_protocol_blocks_get_upload( + uint8_t data_array[], + size_t count_bit_data_array, + LevelDuration* upload, + size_t max_size_upload, + uint32_t duration_bit) { + size_t index_bit = 0; + size_t size_upload = 0; + uint32_t duration = duration_bit; + bool last_bit = subghz_protocol_blocks_get_bit_array(data_array, index_bit++); + for(size_t i = 1; i < count_bit_data_array; i++) { + if(last_bit == subghz_protocol_blocks_get_bit_array(data_array, index_bit)) { + duration += duration_bit; + } else { + furi_assert(max_size_upload > size_upload); + upload[size_upload++] = level_duration_make( + subghz_protocol_blocks_get_bit_array(data_array, index_bit - 1), duration); + last_bit = !last_bit; + duration = duration_bit; + } + index_bit++; + } + upload[size_upload++] = level_duration_make( + subghz_protocol_blocks_get_bit_array(data_array, index_bit - 1), duration); + return size_upload; +} \ No newline at end of file diff --git a/lib/subghz/blocks/encoder.h b/lib/subghz/blocks/encoder.h index 5c84fcd1..80ffe490 100644 --- a/lib/subghz/blocks/encoder.h +++ b/lib/subghz/blocks/encoder.h @@ -14,3 +14,39 @@ typedef struct { LevelDuration* upload; } SubGhzProtocolBlockEncoder; + +/** + * Set data bit when encoding HEX array. + * @param bit_value The value of the bit to be set + * @param data_array Pointer to a HEX array + * @param set_index_bit Number set a bit in the array starting from the left + * @param max_size_array array size, check not to overflow + */ +void subghz_protocol_blocks_set_bit_array( + bool bit_value, + uint8_t data_array[], + size_t set_index_bit, + size_t max_size_array); + +/** + * Get data bit when encoding HEX array. + * @param data_array Pointer to a HEX array + * @param read_index_bit Number get a bit in the array starting from the left + * @return bool value bit + */ +bool subghz_protocol_blocks_get_bit_array(uint8_t data_array[], size_t read_index_bit); + +/** + * Generating an upload from data. + * @param data_array Pointer to a HEX array + * @param count_bit_data_array How many bits in the array are processed + * @param upload Pointer to a LevelDuration + * @param max_size_upload upload size, check not to overflow + * @param duration_bit duration 1 bit + */ +size_t subghz_protocol_blocks_get_upload( + uint8_t data_array[], + size_t count_bit_data_array, + LevelDuration* upload, + size_t max_size_upload, + uint32_t duration_bit); diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c new file mode 100644 index 00000000..23ac8cc8 --- /dev/null +++ b/lib/subghz/protocols/chamberlain_code.c @@ -0,0 +1,483 @@ +#include "chamberlain_code.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolChamb_Code" + +#define CHAMBERLAIN_CODE_BIT_STOP 0b0001 +#define CHAMBERLAIN_CODE_BIT_1 0b0011 +#define CHAMBERLAIN_CODE_BIT_0 0b0111 + +#define CHAMBERLAIN_7_CODE_MASK 0xF000000FF0F +#define CHAMBERLAIN_8_CODE_MASK 0xF00000F00F +#define CHAMBERLAIN_9_CODE_MASK 0xF000000000F + +#define CHAMBERLAIN_7_CODE_MASK_CHECK 0x10000001101 +#define CHAMBERLAIN_8_CODE_MASK_CHECK 0x1000001001 +#define CHAMBERLAIN_9_CODE_MASK_CHECK 0x10000000001 + +#define CHAMBERLAIN_7_CODE_DIP_PATTERN "%c%c%c%c%c%c%c" +#define CHAMBERLAIN_7_CODE_DATA_TO_DIP(dip) \ + (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \ + (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ + (dip & 0x0001 ? '1' : '0') + +#define CHAMBERLAIN_8_CODE_DIP_PATTERN "%c%c%c%c%cx%c%c" +#define CHAMBERLAIN_8_CODE_DATA_TO_DIP(dip) \ + (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), \ + (dip & 0x0010 ? '1' : '0'), (dip & 0x0008 ? '1' : '0'), (dip & 0x0001 ? '1' : '0'), \ + (dip & 0x0002 ? '1' : '0') + +#define CHAMBERLAIN_9_CODE_DIP_PATTERN "%c%c%c%c%c%c%c%c%c" +#define CHAMBERLAIN_9_CODE_DATA_TO_DIP(dip) \ + (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), \ + (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), (dip & 0x0008 ? '1' : '0'), \ + (dip & 0x0001 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), (dip & 0x0004 ? '1' : '0') + +static const SubGhzBlockConst subghz_protocol_chamb_code_const = { + .te_short = 1000, + .te_long = 3000, + .te_delta = 200, + .min_count_bit_for_found = 10, +}; + +struct SubGhzProtocolDecoderChamb_Code { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderChamb_Code { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + Chamb_CodeDecoderStepReset = 0, + Chamb_CodeDecoderStepFoundStartBit, + Chamb_CodeDecoderStepSaveDuration, + Chamb_CodeDecoderStepCheckDuration, +} Chamb_CodeDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_chamb_code_decoder = { + .alloc = subghz_protocol_decoder_chamb_code_alloc, + .free = subghz_protocol_decoder_chamb_code_free, + + .feed = subghz_protocol_decoder_chamb_code_feed, + .reset = subghz_protocol_decoder_chamb_code_reset, + + .get_hash_data = subghz_protocol_decoder_chamb_code_get_hash_data, + .serialize = subghz_protocol_decoder_chamb_code_serialize, + .deserialize = subghz_protocol_decoder_chamb_code_deserialize, + .get_string = subghz_protocol_decoder_chamb_code_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_chamb_code_encoder = { + .alloc = subghz_protocol_encoder_chamb_code_alloc, + .free = subghz_protocol_encoder_chamb_code_free, + + .deserialize = subghz_protocol_encoder_chamb_code_deserialize, + .stop = subghz_protocol_encoder_chamb_code_stop, + .yield = subghz_protocol_encoder_chamb_code_yield, +}; + +const SubGhzProtocol subghz_protocol_chamb_code = { + .name = SUBGHZ_PROTOCOL_CHAMB_CODE_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_chamb_code_decoder, + .encoder = &subghz_protocol_chamb_code_encoder, +}; + +void* subghz_protocol_encoder_chamb_code_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderChamb_Code* instance = malloc(sizeof(SubGhzProtocolEncoderChamb_Code)); + + instance->base.protocol = &subghz_protocol_chamb_code; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 24; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_runing = false; + return instance; +} + +void subghz_protocol_encoder_chamb_code_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderChamb_Code* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static uint64_t subghz_protocol_chamb_bit_to_code(uint64_t data, uint8_t size) { + uint64_t data_res = 0; + for(uint8_t i = 0; i < size; i++) { + if(!(bit_read(data, size - i - 1))) { + data_res = data_res << 4 | CHAMBERLAIN_CODE_BIT_0; + } else { + data_res = data_res << 4 | CHAMBERLAIN_CODE_BIT_1; + } + } + return data_res; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderChamb_Code instance + * @return true On success + */ +static bool + subghz_protocol_encoder_chamb_code_get_upload(SubGhzProtocolEncoderChamb_Code* instance) { + furi_assert(instance); + + uint64_t data = subghz_protocol_chamb_bit_to_code( + instance->generic.data, instance->generic.data_count_bit); + + switch(instance->generic.data_count_bit) { + case 7: + data = ((data >> 4) << 16) | (data & 0xF) << 4 | CHAMBERLAIN_7_CODE_MASK_CHECK; + break; + case 8: + data = ((data >> 12) << 16) | (data & 0xFF) << 4 | CHAMBERLAIN_8_CODE_MASK_CHECK; + break; + case 9: + data = (data << 4) | CHAMBERLAIN_9_CODE_MASK_CHECK; + break; + + default: + furi_crash(TAG " unknown protocol."); + return false; + break; + } +#define UPLOAD_HEX_DATA_SIZE 10 + uint8_t upload_hex_data[UPLOAD_HEX_DATA_SIZE] = {0}; + size_t upload_hex_count_bit = 0; + + //insert guard time + for(uint8_t i = 0; i < 36; i++) { + subghz_protocol_blocks_set_bit_array( + 0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } + + //insert data + switch(instance->generic.data_count_bit) { + case 7: + case 9: + for(uint8_t i = 44; i > 0; i--) { + if(!bit_read(data, i - 1)) { + subghz_protocol_blocks_set_bit_array( + 0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } else { + subghz_protocol_blocks_set_bit_array( + 1, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } + } + break; + case 8: + for(uint8_t i = 40; i > 0; i--) { + if(!bit_read(data, i - 1)) { + subghz_protocol_blocks_set_bit_array( + 0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } else { + subghz_protocol_blocks_set_bit_array( + 1, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE); + } + } + break; + } + + instance->encoder.size_upload = subghz_protocol_blocks_get_upload( + upload_hex_data, + upload_hex_count_bit, + instance->encoder.upload, + instance->encoder.size_upload, + subghz_protocol_chamb_code_const.te_short); + + return true; +} + +bool subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderChamb_Code* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_chamb_code_get_upload(instance); + instance->encoder.is_runing = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_chamb_code_stop(void* context) { + SubGhzProtocolEncoderChamb_Code* instance = context; + instance->encoder.is_runing = false; +} + +LevelDuration subghz_protocol_encoder_chamb_code_yield(void* context) { + SubGhzProtocolEncoderChamb_Code* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { + instance->encoder.is_runing = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_chamb_code_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderChamb_Code* instance = malloc(sizeof(SubGhzProtocolDecoderChamb_Code)); + instance->base.protocol = &subghz_protocol_chamb_code; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_chamb_code_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + free(instance); +} + +void subghz_protocol_decoder_chamb_code_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; +} + +static bool subghz_protocol_chamb_code_to_bit(uint64_t* data, uint8_t size) { + uint64_t data_tmp = data[0]; + uint64_t data_res = 0; + for(uint8_t i = 0; i < size; i++) { + if((data_tmp & 0xF) == CHAMBERLAIN_CODE_BIT_0) { + bit_write(data_res, i, 0); + } else if((data_tmp & 0xF) == CHAMBERLAIN_CODE_BIT_1) { + bit_write(data_res, i, 1); + } else { + return false; + } + data_tmp >>= 4; + } + data[0] = data_res; + return true; +} + +static bool subghz_protocol_decoder_chamb_code_check_mask_and_parse( + SubGhzProtocolDecoderChamb_Code* instance) { + furi_assert(instance); + if(instance->decoder.decode_count_bit > + subghz_protocol_chamb_code_const.min_count_bit_for_found + 1) + return false; + + if((instance->decoder.decode_data & CHAMBERLAIN_7_CODE_MASK) == + CHAMBERLAIN_7_CODE_MASK_CHECK) { + instance->decoder.decode_count_bit = 7; + instance->decoder.decode_data &= ~CHAMBERLAIN_7_CODE_MASK; + instance->decoder.decode_data = (instance->decoder.decode_data >> 12) | + ((instance->decoder.decode_data >> 4) & 0xF); + } else if( + (instance->decoder.decode_data & CHAMBERLAIN_8_CODE_MASK) == + CHAMBERLAIN_8_CODE_MASK_CHECK) { + instance->decoder.decode_count_bit = 8; + instance->decoder.decode_data &= ~CHAMBERLAIN_8_CODE_MASK; + instance->decoder.decode_data = instance->decoder.decode_data >> 4 | + CHAMBERLAIN_CODE_BIT_0 << 8; //DIP 6 no use + } else if( + (instance->decoder.decode_data & CHAMBERLAIN_9_CODE_MASK) == + CHAMBERLAIN_9_CODE_MASK_CHECK) { + instance->decoder.decode_count_bit = 9; + instance->decoder.decode_data &= ~CHAMBERLAIN_9_CODE_MASK; + instance->decoder.decode_data >>= 4; + } else { + return false; + } + return subghz_protocol_chamb_code_to_bit( + &instance->decoder.decode_data, instance->decoder.decode_count_bit); +} + +void subghz_protocol_decoder_chamb_code_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + switch(instance->decoder.parser_step) { + case Chamb_CodeDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 39) < + subghz_protocol_chamb_code_const.te_delta * 20)) { + //Found header Chamb_Code + instance->decoder.parser_step = Chamb_CodeDecoderStepFoundStartBit; + } + break; + case Chamb_CodeDecoderStepFoundStartBit: + if((level) && (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short) < + subghz_protocol_chamb_code_const.te_delta)) { + //Found start bit Chamb_Code + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = instance->decoder.decode_data << 4 | + CHAMBERLAIN_CODE_BIT_STOP; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } + break; + case Chamb_CodeDecoderStepSaveDuration: + if(!level) { //save interval + if(duration > subghz_protocol_chamb_code_const.te_short * 5) { + if(instance->decoder.decode_count_bit >= + subghz_protocol_chamb_code_const.min_count_bit_for_found) { + instance->generic.serial = 0x0; + instance->generic.btn = 0x0; + if(subghz_protocol_decoder_chamb_code_check_mask_and_parse(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Chamb_CodeDecoderStepCheckDuration; + } + } else { + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } + break; + case Chamb_CodeDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF( //Found stop bit Chamb_Code + instance->decoder.te_last, + subghz_protocol_chamb_code_const.te_short * 3) < + subghz_protocol_chamb_code_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short) < + subghz_protocol_chamb_code_const.te_delta)) { + instance->decoder.decode_data = instance->decoder.decode_data << 4 | + CHAMBERLAIN_CODE_BIT_STOP; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_chamb_code_const.te_short * 2) < + subghz_protocol_chamb_code_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 2) < + subghz_protocol_chamb_code_const.te_delta)) { + instance->decoder.decode_data = instance->decoder.decode_data << 4 | + CHAMBERLAIN_CODE_BIT_1; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_chamb_code_const.te_short) < + subghz_protocol_chamb_code_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 3) < + subghz_protocol_chamb_code_const.te_delta)) { + instance->decoder.decode_data = instance->decoder.decode_data << 4 | + CHAMBERLAIN_CODE_BIT_0; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } + + } else { + instance->decoder.parser_step = Chamb_CodeDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_chamb_code_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_chamb_code_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderChamb_Code* instance = context; + + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%03lX\r\n" + "Yek:0x%03lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_lo, + code_found_reverse_lo); + + switch(instance->generic.data_count_bit) { + case 7: + string_cat_printf( + output, + "DIP:" CHAMBERLAIN_7_CODE_DIP_PATTERN "\r\n", + CHAMBERLAIN_7_CODE_DATA_TO_DIP(code_found_lo)); + break; + case 8: + string_cat_printf( + output, + "DIP:" CHAMBERLAIN_8_CODE_DIP_PATTERN "\r\n", + CHAMBERLAIN_8_CODE_DATA_TO_DIP(code_found_lo)); + break; + case 9: + string_cat_printf( + output, + "DIP:" CHAMBERLAIN_9_CODE_DIP_PATTERN "\r\n", + CHAMBERLAIN_9_CODE_DATA_TO_DIP(code_found_lo)); + break; + + default: + break; + } +} diff --git a/lib/subghz/protocols/chamberlain_code.h b/lib/subghz/protocols/chamberlain_code.h new file mode 100644 index 00000000..f6ef48fd --- /dev/null +++ b/lib/subghz/protocols/chamberlain_code.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_CHAMB_CODE_NAME "Cham_Code" + +typedef struct SubGhzProtocolDecoderChamb_Code SubGhzProtocolDecoderChamb_Code; +typedef struct SubGhzProtocolEncoderChamb_Code SubGhzProtocolEncoderChamb_Code; + +extern const SubGhzProtocolDecoder subghz_protocol_chamb_code_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_chamb_code_encoder; +extern const SubGhzProtocol subghz_protocol_chamb_code; + +/** + * Allocate SubGhzProtocolEncoderChamb_Code. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderChamb_Code* pointer to a SubGhzProtocolEncoderChamb_Code instance + */ +void* subghz_protocol_encoder_chamb_code_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderChamb_Code. + * @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance + */ +void subghz_protocol_encoder_chamb_code_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance + */ +void subghz_protocol_encoder_chamb_code_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_chamb_code_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderChamb_Code. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderChamb_Code* pointer to a SubGhzProtocolDecoderChamb_Code instance + */ +void* subghz_protocol_decoder_chamb_code_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderChamb_Code. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + */ +void subghz_protocol_decoder_chamb_code_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderChamb_Code. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + */ +void subghz_protocol_decoder_chamb_code_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_chamb_code_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderChamb_Code. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ +bool subghz_protocol_decoder_chamb_code_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderChamb_Code. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance + * @param output Resulting text + */ +void subghz_protocol_decoder_chamb_code_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/firefly.h b/lib/subghz/protocols/firefly.h deleted file mode 100644 index abb4537c..00000000 --- a/lib/subghz/protocols/firefly.h +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once - -#include "base.h" - -#define SUBGHZ_PROTOCOL_FIREFLY_NAME "Firefly" - -typedef struct SubGhzProtocolDecoderFirefly SubGhzProtocolDecoderFirefly; -typedef struct SubGhzProtocolEncoderFirefly SubGhzProtocolEncoderFirefly; - -extern const SubGhzProtocolDecoder subghz_protocol_firefly_decoder; -extern const SubGhzProtocolEncoder subghz_protocol_firefly_encoder; -extern const SubGhzProtocol subghz_protocol_firefly; - -/** - * Allocate SubGhzProtocolEncoderFirefly. - * @param environment Pointer to a SubGhzEnvironment instance - * @return SubGhzProtocolEncoderFirefly* pointer to a SubGhzProtocolEncoderFirefly instance - */ -void* subghz_protocol_encoder_firefly_alloc(SubGhzEnvironment* environment); - -/** - * Free SubGhzProtocolEncoderFirefly. - * @param context Pointer to a SubGhzProtocolEncoderFirefly instance - */ -void subghz_protocol_encoder_firefly_free(void* context); - -/** - * Deserialize and generating an upload to send. - * @param context Pointer to a SubGhzProtocolEncoderFirefly instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success - */ -bool subghz_protocol_encoder_firefly_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Forced transmission stop. - * @param context Pointer to a SubGhzProtocolEncoderFirefly instance - */ -void subghz_protocol_encoder_firefly_stop(void* context); - -/** - * Getting the level and duration of the upload to be loaded into DMA. - * @param context Pointer to a SubGhzProtocolEncoderFirefly instance - * @return LevelDuration - */ -LevelDuration subghz_protocol_encoder_firefly_yield(void* context); - -/** - * Allocate SubGhzProtocolDecoderFirefly. - * @param environment Pointer to a SubGhzEnvironment instance - * @return SubGhzProtocolDecoderFirefly* pointer to a SubGhzProtocolDecoderFirefly instance - */ -void* subghz_protocol_decoder_firefly_alloc(SubGhzEnvironment* environment); - -/** - * Free SubGhzProtocolDecoderFirefly. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - */ -void subghz_protocol_decoder_firefly_free(void* context); - -/** - * Reset decoder SubGhzProtocolDecoderFirefly. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - */ -void subghz_protocol_decoder_firefly_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void subghz_protocol_decoder_firefly_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - * @return hash Hash sum - */ -uint8_t subghz_protocol_decoder_firefly_get_hash_data(void* context); - -/** - * Serialize data SubGhzProtocolDecoderFirefly. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param frequency The frequency at which the signal was received, Hz - * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset - * @return true On success - */ -bool subghz_protocol_decoder_firefly_serialize( - void* context, - FlipperFormat* flipper_format, - uint32_t frequency, - FuriHalSubGhzPreset preset); - -/** - * Deserialize data SubGhzProtocolDecoderFirefly. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success - */ -bool subghz_protocol_decoder_firefly_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a SubGhzProtocolDecoderFirefly instance - * @param output Resulting text - */ -void subghz_protocol_decoder_firefly_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/firefly.c b/lib/subghz/protocols/linear.c similarity index 55% rename from lib/subghz/protocols/firefly.c rename to lib/subghz/protocols/linear.c index 21047a30..6b013278 100644 --- a/lib/subghz/protocols/firefly.c +++ b/lib/subghz/protocols/linear.c @@ -1,4 +1,4 @@ -#include "firefly.h" +#include "linear.h" #include "../blocks/const.h" #include "../blocks/decoder.h" @@ -6,7 +6,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocolFirefly" +#define TAG "SubGhzProtocolLinear" #define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" #define DATA_TO_DIP(dip) \ @@ -15,21 +15,21 @@ (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ (dip & 0x0001 ? '1' : '0') -static const SubGhzBlockConst subghz_protocol_firefly_const = { +static const SubGhzBlockConst subghz_protocol_linear_const = { .te_short = 500, .te_long = 1500, .te_delta = 150, .min_count_bit_for_found = 10, }; -struct SubGhzProtocolDecoderFirefly { +struct SubGhzProtocolDecoderLinear { SubGhzProtocolDecoderBase base; SubGhzBlockDecoder decoder; SubGhzBlockGeneric generic; }; -struct SubGhzProtocolEncoderFirefly { +struct SubGhzProtocolEncoderLinear { SubGhzProtocolEncoderBase base; SubGhzProtocolBlockEncoder encoder; @@ -37,48 +37,48 @@ struct SubGhzProtocolEncoderFirefly { }; typedef enum { - FireflyDecoderStepReset = 0, - FireflyDecoderStepSaveDuration, - FireflyDecoderStepCheckDuration, -} FireflyDecoderStep; + LinearDecoderStepReset = 0, + LinearDecoderStepSaveDuration, + LinearDecoderStepCheckDuration, +} LinearDecoderStep; -const SubGhzProtocolDecoder subghz_protocol_firefly_decoder = { - .alloc = subghz_protocol_decoder_firefly_alloc, - .free = subghz_protocol_decoder_firefly_free, +const SubGhzProtocolDecoder subghz_protocol_linear_decoder = { + .alloc = subghz_protocol_decoder_linear_alloc, + .free = subghz_protocol_decoder_linear_free, - .feed = subghz_protocol_decoder_firefly_feed, - .reset = subghz_protocol_decoder_firefly_reset, + .feed = subghz_protocol_decoder_linear_feed, + .reset = subghz_protocol_decoder_linear_reset, - .get_hash_data = subghz_protocol_decoder_firefly_get_hash_data, - .serialize = subghz_protocol_decoder_firefly_serialize, - .deserialize = subghz_protocol_decoder_firefly_deserialize, - .get_string = subghz_protocol_decoder_firefly_get_string, + .get_hash_data = subghz_protocol_decoder_linear_get_hash_data, + .serialize = subghz_protocol_decoder_linear_serialize, + .deserialize = subghz_protocol_decoder_linear_deserialize, + .get_string = subghz_protocol_decoder_linear_get_string, }; -const SubGhzProtocolEncoder subghz_protocol_firefly_encoder = { - .alloc = subghz_protocol_encoder_firefly_alloc, - .free = subghz_protocol_encoder_firefly_free, +const SubGhzProtocolEncoder subghz_protocol_linear_encoder = { + .alloc = subghz_protocol_encoder_linear_alloc, + .free = subghz_protocol_encoder_linear_free, - .deserialize = subghz_protocol_encoder_firefly_deserialize, - .stop = subghz_protocol_encoder_firefly_stop, - .yield = subghz_protocol_encoder_firefly_yield, + .deserialize = subghz_protocol_encoder_linear_deserialize, + .stop = subghz_protocol_encoder_linear_stop, + .yield = subghz_protocol_encoder_linear_yield, }; -const SubGhzProtocol subghz_protocol_firefly = { - .name = SUBGHZ_PROTOCOL_FIREFLY_NAME, +const SubGhzProtocol subghz_protocol_linear = { + .name = SUBGHZ_PROTOCOL_LINEAR_NAME, .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, - .decoder = &subghz_protocol_firefly_decoder, - .encoder = &subghz_protocol_firefly_encoder, + .decoder = &subghz_protocol_linear_decoder, + .encoder = &subghz_protocol_linear_encoder, }; -void* subghz_protocol_encoder_firefly_alloc(SubGhzEnvironment* environment) { +void* subghz_protocol_encoder_linear_alloc(SubGhzEnvironment* environment) { UNUSED(environment); - SubGhzProtocolEncoderFirefly* instance = malloc(sizeof(SubGhzProtocolEncoderFirefly)); + SubGhzProtocolEncoderLinear* instance = malloc(sizeof(SubGhzProtocolEncoderLinear)); - instance->base.protocol = &subghz_protocol_firefly; + instance->base.protocol = &subghz_protocol_linear; instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 10; @@ -88,19 +88,19 @@ void* subghz_protocol_encoder_firefly_alloc(SubGhzEnvironment* environment) { return instance; } -void subghz_protocol_encoder_firefly_free(void* context) { +void subghz_protocol_encoder_linear_free(void* context) { furi_assert(context); - SubGhzProtocolEncoderFirefly* instance = context; + SubGhzProtocolEncoderLinear* instance = context; free(instance->encoder.upload); free(instance); } /** * Generating an upload from data. - * @param instance Pointer to a SubGhzProtocolEncoderFirefly instance + * @param instance Pointer to a SubGhzProtocolEncoderLinear instance * @return true On success */ -static bool subghz_protocol_encoder_firefly_get_upload(SubGhzProtocolEncoderFirefly* instance) { +static bool subghz_protocol_encoder_linear_get_upload(SubGhzProtocolEncoderLinear* instance) { furi_assert(instance); size_t index = 0; size_t size_upload = (instance->generic.data_count_bit * 2); @@ -116,40 +116,40 @@ static bool subghz_protocol_encoder_firefly_get_upload(SubGhzProtocolEncoderFire if(bit_read(instance->generic.data, i - 1)) { //send bit 1 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_firefly_const.te_short * 3); + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short * 3); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_firefly_const.te_short); + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short); } else { //send bit 0 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_firefly_const.te_short); + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_firefly_const.te_short * 3); + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 3); } } //Send end bit if(bit_read(instance->generic.data, 0)) { //send bit 1 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_firefly_const.te_short * 3); + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short * 3); //Send PT_GUARD instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_firefly_const.te_short * 42); + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 42); } else { //send bit 0 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_firefly_const.te_short); + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); //Send PT_GUARD instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_firefly_const.te_short * 44); + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 44); } return true; } -bool subghz_protocol_encoder_firefly_deserialize(void* context, FlipperFormat* flipper_format) { +bool subghz_protocol_encoder_linear_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); - SubGhzProtocolEncoderFirefly* instance = context; + SubGhzProtocolEncoderLinear* instance = context; bool res = false; do { if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { @@ -161,7 +161,7 @@ bool subghz_protocol_encoder_firefly_deserialize(void* context, FlipperFormat* f flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_firefly_get_upload(instance); + subghz_protocol_encoder_linear_get_upload(instance); instance->encoder.is_runing = true; res = true; @@ -170,13 +170,13 @@ bool subghz_protocol_encoder_firefly_deserialize(void* context, FlipperFormat* f return res; } -void subghz_protocol_encoder_firefly_stop(void* context) { - SubGhzProtocolEncoderFirefly* instance = context; +void subghz_protocol_encoder_linear_stop(void* context) { + SubGhzProtocolEncoderLinear* instance = context; instance->encoder.is_runing = false; } -LevelDuration subghz_protocol_encoder_firefly_yield(void* context) { - SubGhzProtocolEncoderFirefly* instance = context; +LevelDuration subghz_protocol_encoder_linear_yield(void* context) { + SubGhzProtocolEncoderLinear* instance = context; if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { instance->encoder.is_runing = false; @@ -193,68 +193,66 @@ LevelDuration subghz_protocol_encoder_firefly_yield(void* context) { return ret; } -void* subghz_protocol_decoder_firefly_alloc(SubGhzEnvironment* environment) { +void* subghz_protocol_decoder_linear_alloc(SubGhzEnvironment* environment) { UNUSED(environment); - SubGhzProtocolDecoderFirefly* instance = malloc(sizeof(SubGhzProtocolDecoderFirefly)); - instance->base.protocol = &subghz_protocol_firefly; + SubGhzProtocolDecoderLinear* instance = malloc(sizeof(SubGhzProtocolDecoderLinear)); + instance->base.protocol = &subghz_protocol_linear; instance->generic.protocol_name = instance->base.protocol->name; return instance; } -void subghz_protocol_decoder_firefly_free(void* context) { +void subghz_protocol_decoder_linear_free(void* context) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; + SubGhzProtocolDecoderLinear* instance = context; free(instance); } -void subghz_protocol_decoder_firefly_reset(void* context) { +void subghz_protocol_decoder_linear_reset(void* context) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; - instance->decoder.parser_step = FireflyDecoderStepReset; + SubGhzProtocolDecoderLinear* instance = context; + instance->decoder.parser_step = LinearDecoderStepReset; } -void subghz_protocol_decoder_firefly_feed(void* context, bool level, uint32_t duration) { +void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t duration) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; + SubGhzProtocolDecoderLinear* instance = context; switch(instance->decoder.parser_step) { - case FireflyDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, subghz_protocol_firefly_const.te_short * 42) < - subghz_protocol_firefly_const.te_delta * 20)) { - //Found header Firefly + case LinearDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_linear_const.te_short * 42) < + subghz_protocol_linear_const.te_delta * 20)) { + //Found header Linear instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; - instance->decoder.parser_step = FireflyDecoderStepSaveDuration; + instance->decoder.parser_step = LinearDecoderStepSaveDuration; } break; - case FireflyDecoderStepSaveDuration: + case LinearDecoderStepSaveDuration: if(level) { instance->decoder.te_last = duration; - instance->decoder.parser_step = FireflyDecoderStepCheckDuration; + instance->decoder.parser_step = LinearDecoderStepCheckDuration; } else { - instance->decoder.parser_step = FireflyDecoderStepReset; + instance->decoder.parser_step = LinearDecoderStepReset; } break; - case FireflyDecoderStepCheckDuration: + case LinearDecoderStepCheckDuration: if(!level) { //save interval - if(duration >= (subghz_protocol_firefly_const.te_short * 5)) { - instance->decoder.parser_step = FireflyDecoderStepReset; + if(duration >= (subghz_protocol_linear_const.te_short * 5)) { + instance->decoder.parser_step = LinearDecoderStepReset; //checking that the duration matches the guardtime - if((DURATION_DIFF(duration, subghz_protocol_firefly_const.te_short * 42) > - subghz_protocol_firefly_const.te_delta * 20)) { + if((DURATION_DIFF(duration, subghz_protocol_linear_const.te_short * 42) > + subghz_protocol_linear_const.te_delta * 20)) { break; } - if(DURATION_DIFF( - instance->decoder.te_last, subghz_protocol_firefly_const.te_short) < - subghz_protocol_firefly_const.te_delta) { + if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_short) < + subghz_protocol_linear_const.te_delta) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); } else if( - DURATION_DIFF( - instance->decoder.te_last, subghz_protocol_firefly_const.te_long) < - subghz_protocol_firefly_const.te_delta) { + DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_long) < + subghz_protocol_linear_const.te_delta) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); } if(instance->decoder.decode_count_bit == - subghz_protocol_firefly_const.min_count_bit_for_found) { + subghz_protocol_linear_const.min_count_bit_for_found) { instance->generic.serial = 0x0; instance->generic.btn = 0x0; @@ -267,56 +265,56 @@ void subghz_protocol_decoder_firefly_feed(void* context, bool level, uint32_t du break; } - if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_firefly_const.te_short) < - subghz_protocol_firefly_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_firefly_const.te_long) < - subghz_protocol_firefly_const.te_delta)) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_short) < + subghz_protocol_linear_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_linear_const.te_long) < + subghz_protocol_linear_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = FireflyDecoderStepSaveDuration; + instance->decoder.parser_step = LinearDecoderStepSaveDuration; } else if( - (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_firefly_const.te_long) < - subghz_protocol_firefly_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_firefly_const.te_short) < - subghz_protocol_firefly_const.te_delta)) { + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_long) < + subghz_protocol_linear_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_linear_const.te_short) < + subghz_protocol_linear_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = FireflyDecoderStepSaveDuration; + instance->decoder.parser_step = LinearDecoderStepSaveDuration; } else { - instance->decoder.parser_step = FireflyDecoderStepReset; + instance->decoder.parser_step = LinearDecoderStepReset; } } else { - instance->decoder.parser_step = FireflyDecoderStepReset; + instance->decoder.parser_step = LinearDecoderStepReset; } break; } } -uint8_t subghz_protocol_decoder_firefly_get_hash_data(void* context) { +uint8_t subghz_protocol_decoder_linear_get_hash_data(void* context) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; + SubGhzProtocolDecoderLinear* instance = context; return subghz_protocol_blocks_get_hash_data( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_firefly_serialize( +bool subghz_protocol_decoder_linear_serialize( void* context, FlipperFormat* flipper_format, uint32_t frequency, FuriHalSubGhzPreset preset) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; + SubGhzProtocolDecoderLinear* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); } -bool subghz_protocol_decoder_firefly_deserialize(void* context, FlipperFormat* flipper_format) { +bool subghz_protocol_decoder_linear_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; + SubGhzProtocolDecoderLinear* instance = context; return subghz_block_generic_deserialize(&instance->generic, flipper_format); } -void subghz_protocol_decoder_firefly_get_string(void* context, string_t output) { +void subghz_protocol_decoder_linear_get_string(void* context, string_t output) { furi_assert(context); - SubGhzProtocolDecoderFirefly* instance = context; + SubGhzProtocolDecoderLinear* instance = context; uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; diff --git a/lib/subghz/protocols/linear.h b/lib/subghz/protocols/linear.h new file mode 100644 index 00000000..44f4e7dd --- /dev/null +++ b/lib/subghz/protocols/linear.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_LINEAR_NAME "Linear" + +typedef struct SubGhzProtocolDecoderLinear SubGhzProtocolDecoderLinear; +typedef struct SubGhzProtocolEncoderLinear SubGhzProtocolEncoderLinear; + +extern const SubGhzProtocolDecoder subghz_protocol_linear_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_linear_encoder; +extern const SubGhzProtocol subghz_protocol_linear; + +/** + * Allocate SubGhzProtocolEncoderLinear. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderLinear* pointer to a SubGhzProtocolEncoderLinear instance + */ +void* subghz_protocol_encoder_linear_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderLinear. + * @param context Pointer to a SubGhzProtocolEncoderLinear instance + */ +void subghz_protocol_encoder_linear_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderLinear instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_linear_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderLinear instance + */ +void subghz_protocol_encoder_linear_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderLinear instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_linear_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderLinear. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderLinear* pointer to a SubGhzProtocolDecoderLinear instance + */ +void* subghz_protocol_decoder_linear_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderLinear. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + */ +void subghz_protocol_decoder_linear_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderLinear. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + */ +void subghz_protocol_decoder_linear_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_linear_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderLinear. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ +bool subghz_protocol_decoder_linear_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderLinear. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_linear_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderLinear instance + * @param output Resulting text + */ +void subghz_protocol_decoder_linear_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index 6f6240a6..f8d42b29 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -247,7 +247,7 @@ void subghz_protocol_decoder_megacode_feed(void* context, bool level, uint32_t d switch(instance->decoder.parser_step) { case MegaCodeDecoderStepReset: if((!level) && (DURATION_DIFF(duration, subghz_protocol_megacode_const.te_short * 13) < - subghz_protocol_megacode_const.te_delta * 15)) { //10..16ms + subghz_protocol_megacode_const.te_delta * 17)) { //10..16ms //Found header MegaCode instance->decoder.parser_step = MegaCodeDecoderStepFoundStartBit; } diff --git a/lib/subghz/protocols/registry.c b/lib/subghz/protocols/registry.c index b90e535c..668e6ad5 100644 --- a/lib/subghz/protocols/registry.c +++ b/lib/subghz/protocols/registry.c @@ -7,8 +7,9 @@ const SubGhzProtocol* subghz_protocol_registry[] = { &subghz_protocol_nero_sketch, &subghz_protocol_ido, &subghz_protocol_kia, &subghz_protocol_hormann, &subghz_protocol_nero_radio, &subghz_protocol_somfy_telis, &subghz_protocol_somfy_keytis, &subghz_protocol_scher_khan, &subghz_protocol_princeton, - &subghz_protocol_raw, &subghz_protocol_firefly, &subghz_protocol_secplus_v2, + &subghz_protocol_raw, &subghz_protocol_linear, &subghz_protocol_secplus_v2, &subghz_protocol_secplus_v1, &subghz_protocol_megacode, &subghz_protocol_holtek, + &subghz_protocol_chamb_code, }; diff --git a/lib/subghz/protocols/registry.h b/lib/subghz/protocols/registry.h index a8bc9af4..39322380 100644 --- a/lib/subghz/protocols/registry.h +++ b/lib/subghz/protocols/registry.h @@ -21,11 +21,12 @@ #include "scher_khan.h" #include "gate_tx.h" #include "raw.h" -#include "firefly.h" +#include "linear.h" #include "secplus_v2.h" #include "secplus_v1.h" #include "megacode.h" #include "holtek.h" +#include "chamberlain_code.h" /** * Registration by name SubGhzProtocol. From 40e3df99790b8a5d0699fadaabd79e2bb9138e32 Mon Sep 17 00:00:00 2001 From: SG Date: Thu, 2 Jun 2022 22:56:03 +1000 Subject: [PATCH 27/28] iButton, RFID: rc-59 fixes (#1291) * iButton: correct rename logic * LFRFID: remove debug info, correct text --- applications/ibutton/ibutton.c | 9 +++++---- .../lfrfid/scene/lfrfid_app_scene_read_success.cpp | 2 +- .../lfrfid/scene/lfrfid_app_scene_save_data.cpp | 13 ------------- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/applications/ibutton/ibutton.c b/applications/ibutton/ibutton.c index b1b5bf62..8713919b 100644 --- a/applications/ibutton/ibutton.c +++ b/applications/ibutton/ibutton.c @@ -211,11 +211,12 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { bool result = false; do { - // First remove key if it was saved (we rename the key) - ibutton_delete_key(ibutton); - - // Set full file name, for new key + // Check if we has old key if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + // First remove old key + ibutton_delete_key(ibutton); + + // Remove old key name from path size_t filename_start = string_search_rchar(ibutton->file_path, '/'); string_left(ibutton->file_path, filename_start); } diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp b/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp index fd6f46a0..010cac2c 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp @@ -81,7 +81,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ case LfrfidKeyType::KeyIoProxXSF: line_1_text->set_text("HEX:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary); line_2l_text->set_text("FC:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); - line_2r_text->set_text("V:", 95, 35, 0, AlignRight, AlignBottom, FontSecondary); + line_2r_text->set_text("VС:", 95, 35, 0, AlignRight, AlignBottom, FontSecondary); line_3_text->set_text("Card:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary); for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp index b36ff7e3..3a13e683 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp @@ -1,23 +1,10 @@ #include "lfrfid_app_scene_save_data.h" #include -static void print_buffer(const uint8_t* buffer) { - for(uint8_t i = 0; i < LFRFID_KEY_SIZE; i++) { - printf("%02X", buffer[i]); - } -} - void LfRfidAppSceneSaveData::on_enter(LfRfidApp* app, bool need_restore) { auto byte_input = app->view_controller.get(); RfidKey& key = app->worker.key; - printf("k: "); - print_buffer(key.get_data()); - printf(" o: "); - print_buffer(old_key_data); - printf(" n: "); - print_buffer(new_key_data); - printf("\r\n"); if(need_restore) printf("restored\r\n"); if(need_restore) { From 8b54436950c5623aa442680d2ac305bfae309172 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 3 Jun 2022 11:46:25 +0400 Subject: [PATCH 28/28] [FL-2564]SubGhz: 0.59.0-rc bugfixes (#1292) --- .../subghz/helpers/subghz_custom_event.h | 20 +++++++++++++++++++ .../scenes/subghz_scene_delete_success.c | 10 ++++++++-- .../subghz/scenes/subghz_scene_read_raw.c | 10 ++++++---- .../subghz/scenes/subghz_scene_set_type.c | 20 ------------------- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/applications/subghz/helpers/subghz_custom_event.h b/applications/subghz/helpers/subghz_custom_event.h index ad70be7e..a2e5ee3e 100644 --- a/applications/subghz/helpers/subghz_custom_event.h +++ b/applications/subghz/helpers/subghz_custom_event.h @@ -5,6 +5,26 @@ typedef enum { SubGhzCustomEventManagerSet, SubGhzCustomEventManagerSetRAW, + //SubmenuIndex + SubmenuIndexPricenton, + SubmenuIndexNiceFlo12bit, + SubmenuIndexNiceFlo24bit, + SubmenuIndexCAME12bit, + SubmenuIndexCAME24bit, + SubmenuIndexCAMETwee, + SubmenuIndexNeroSketch, + SubmenuIndexNeroRadio, + SubmenuIndexGateTX, + SubmenuIndexDoorHan_315_00, + SubmenuIndexDoorHan_433_92, + SubmenuIndexLinear_300_00, + SubmenuIndexLiftMaster_315_00, + SubmenuIndexLiftMaster_390_00, + SubmenuIndexSecPlus_v2_310_00, + SubmenuIndexSecPlus_v2_315_00, + SubmenuIndexSecPlus_v2_390_00, + + //SubGhzCustomEvent SubGhzCustomEventSceneDeleteSuccess = 100, SubGhzCustomEventSceneDelete, SubGhzCustomEventSceneDeleteRAW, diff --git a/applications/subghz/scenes/subghz_scene_delete_success.c b/applications/subghz/scenes/subghz_scene_delete_success.c index d5b3ec2d..d6e1f8dd 100644 --- a/applications/subghz/scenes/subghz_scene_delete_success.c +++ b/applications/subghz/scenes/subghz_scene_delete_success.c @@ -26,9 +26,15 @@ bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneDeleteSuccess) { - if(!scene_manager_search_and_switch_to_previous_scene( - subghz->scene_manager, SubGhzSceneSaved)) { + if(scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneReadRAW)) { + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); + } else if(scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneSaved)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); + } else { + scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneStart); } return true; } diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/subghz/scenes/subghz_scene_read_raw.c index ae3abd6a..97a07140 100644 --- a/applications/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/subghz/scenes/subghz_scene_read_raw.c @@ -167,11 +167,13 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { break; case SubGhzCustomEventViewReadRAWErase: - subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; - if(subghz_scene_read_raw_update_filename(subghz)) { - string_set(subghz->file_path_tmp, subghz->file_path); - subghz_delete_file(subghz); + if(subghz->txrx->rx_key_state == SubGhzRxKeyStateAddKey) { + if(subghz_scene_read_raw_update_filename(subghz)) { + string_set(subghz->file_path_tmp, subghz->file_path); + subghz_delete_file(subghz); + } } + subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; notification_message(subghz->notifications, &sequence_reset_rgb); return true; break; diff --git a/applications/subghz/scenes/subghz_scene_set_type.c b/applications/subghz/scenes/subghz_scene_set_type.c index a6f0c3ea..a7a4491e 100644 --- a/applications/subghz/scenes/subghz_scene_set_type.c +++ b/applications/subghz/scenes/subghz_scene_set_type.c @@ -10,26 +10,6 @@ #define TAG "SubGhzSetType" -enum SubmenuIndex { - SubmenuIndexPricenton, - SubmenuIndexNiceFlo12bit, - SubmenuIndexNiceFlo24bit, - SubmenuIndexCAME12bit, - SubmenuIndexCAME24bit, - SubmenuIndexCAMETwee, - SubmenuIndexNeroSketch, - SubmenuIndexNeroRadio, - SubmenuIndexGateTX, - SubmenuIndexDoorHan_315_00, - SubmenuIndexDoorHan_433_92, - SubmenuIndexLinear_300_00, - SubmenuIndexLiftMaster_315_00, - SubmenuIndexLiftMaster_390_00, - SubmenuIndexSecPlus_v2_310_00, - SubmenuIndexSecPlus_v2_315_00, - SubmenuIndexSecPlus_v2_390_00, -}; - bool subghz_scene_set_type_submenu_gen_data_protocol( void* context, const char* protocol_name,