From c10cef3336cc0ae03eee506177f5d8a771f9bcc8 Mon Sep 17 00:00:00 2001 From: clipboard1 Date: Tue, 16 May 2023 19:15:50 +0500 Subject: [PATCH 01/40] Moved pmkid on aps and channel to one section I think this solution looks better than two buttons for pmkid --- .../scenes/wifi_marauder_scene_start.c | 17 +++++------------ .../wifi_marauder_app_i.h | 2 +- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c index 0ec122ad9..6f22d0adb 100644 --- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c +++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c @@ -97,20 +97,13 @@ const WifiMarauderItem items[NUM_MENU_ITEMS] = { NO_ARGS, FOCUS_CONSOLE_END, SHOW_STOPSCAN_TIP}, - {"Sniff PMKID on channel", - {""}, - 1, - {"sniffpmkid -c"}, - INPUT_ARGS, + {"Sniff PMKID", + {"ap", "channel"}, + 2, + {"sniffpmkid -d -l", "sniffpmkid -c"}, + TOGGLE_ARGS, FOCUS_CONSOLE_END, SHOW_STOPSCAN_TIP}, - {"Sniff pmkid on selected aps", - {""}, - 1, - {"sniffpmkid -d -l"}, - NO_ARGS, - FOCUS_CONSOLE_END, - NO_TIP}, {"Channel", {"get", "set"}, 2, diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h b/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h index e37e5733b..2a16522bb 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h @@ -26,7 +26,7 @@ #include #include -#define NUM_MENU_ITEMS (19) +#define NUM_MENU_ITEMS (18) #define WIFI_MARAUDER_TEXT_BOX_STORE_SIZE (4096) #define WIFI_MARAUDER_TEXT_INPUT_STORE_SIZE (512) From 7fc8f6b3b86bf92ecb6d11dfba12d2371bac40c9 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 01:27:24 +0300 Subject: [PATCH 02/40] Fix vscode example config --- .vscode/example/tasks.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index 28e67d456..16437cb10 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -13,7 +13,7 @@ "label": "[Debug] Build", "group": "build", "type": "shell", - "command": "./fbt" + "command": "./fbt FIRMWARE_APP_SET=debug_pack" }, { "label": "[Release] Flash (ST-Link)", @@ -25,7 +25,7 @@ "label": "[Debug] Flash (ST-Link)", "group": "build", "type": "shell", - "command": "./fbt FORCE=1 flash" + "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 flash" }, { "label": "[Release] Flash (blackmagic)", @@ -37,7 +37,7 @@ "label": "[Debug] Flash (blackmagic)", "group": "build", "type": "shell", - "command": "./fbt FORCE=1 flash_blackmagic" + "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 flash_blackmagic" }, { "label": "[Release] Flash (JLink)", @@ -49,7 +49,7 @@ "label": "[Debug] Flash (JLink)", "group": "build", "type": "shell", - "command": "./fbt FORCE=1 jflash" + "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 jflash" }, { "label": "[Release] Build update bundle", @@ -61,7 +61,7 @@ "label": "[Debug] Build update bundle", "group": "build", "type": "shell", - "command": "./fbt updater_package" + "command": "./fbt FIRMWARE_APP_SET=debug_pack updater_package" }, { "label": "[Release] Build updater", @@ -73,13 +73,13 @@ "label": "[Debug] Build updater", "group": "build", "type": "shell", - "command": "./fbt updater_all" + "command": "./fbt FIRMWARE_APP_SET=debug_pack updater_all" }, { "label": "[Debug] Flash (USB, w/o resources)", "group": "build", "type": "shell", - "command": "./fbt FORCE=1 flash_usb" + "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 flash_usb" }, { "label": "[Release] Flash (USB, w/o resources)", @@ -97,7 +97,7 @@ "label": "[Debug] Flash (USB, with resources)", "group": "build", "type": "shell", - "command": "./fbt FORCE=1 flash_usb_full" + "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 flash_usb_full" }, { "label": "[Release] Flash (USB, with resources)", From 26a113292b2b4431ccabe317a38342795efc51e5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 13:15:59 +0300 Subject: [PATCH 03/40] CAME Atomo - Add manually support --- .../main/subghz/helpers/subghz_custom_event.h | 2 + .../helpers/subghz_txrx_create_protocol_key.c | 28 +++++++++++++ .../helpers/subghz_txrx_create_protocol_key.h | 7 ++++ .../subghz/scenes/subghz_scene_set_type.c | 20 ++++++++++ firmware/targets/f7/api_symbols.csv | 1 + lib/subghz/protocols/came_atomo.c | 40 +++++++++++++++++++ lib/subghz/protocols/came_atomo.h | 16 ++++++++ 7 files changed, 114 insertions(+) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index cd163a653..bf3c71d3a 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -36,6 +36,8 @@ typedef enum { SubmenuIndexCAME24bit868, SubmenuIndexCAMETwee, SubmenuIndexCAMESpace, + SubmenuIndexCameAtomo433, + SubmenuIndexCameAtomo868, SubmenuIndexPricenton433, SubmenuIndexPricenton315, SubmenuIndexBETT_433, diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index 0631c7c15..4854bc882 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -266,6 +266,34 @@ bool subghz_txrx_gen_alutech_at_4n_protocol( return res; } +bool subghz_txrx_gen_came_atomo_protocol( + void* context, + const char* preset_name, + uint32_t frequency, + uint32_t serial, + uint16_t cnt) { + SubGhzTxRx* txrx = context; + + bool res = false; + + txrx->transmitter = + subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_CAME_ATOMO_NAME); + subghz_txrx_set_preset(txrx, preset_name, frequency, NULL, 0); + + if(txrx->transmitter && subghz_protocol_came_atomo_create_data( + subghz_transmitter_get_protocol_instance(txrx->transmitter), + txrx->fff_data, + serial, + cnt, + txrx->preset)) { + res = true; + } + + subghz_transmitter_free(txrx->transmitter); + + return res; +} + bool subghz_txrx_gen_somfy_telis_protocol( void* context, const char* preset_name, diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h index a5fa2a802..dc7dfbe7e 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -108,6 +108,13 @@ bool subghz_txrx_gen_somfy_telis_protocol( uint8_t btn, uint16_t cnt); +bool subghz_txrx_gen_came_atomo_protocol( + void* context, + const char* preset_name, + uint32_t frequency, + uint32_t serial, + uint16_t cnt); + /** * Generate data SecPlus v2 protocol * diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index ef53006da..14d8ba0d8 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -199,6 +199,18 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexCAMETwee, subghz_scene_set_type_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "CAME Atomo 433MHz", + SubmenuIndexCameAtomo433, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "CAME Atomo 868MHz", + SubmenuIndexCameAtomo868, + subghz_scene_set_type_submenu_callback, + subghz); submenu_add_item( subghz->submenu, "KL: CAME Space 433MHz", @@ -534,6 +546,14 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } break; + case SubmenuIndexCameAtomo433: + generated_protocol = subghz_txrx_gen_came_atomo_protocol( + subghz->txrx, "AM650", 433920000, (key & 0x0FFFFFFF) | 0x10000000, 0x0003); + break; + case SubmenuIndexCameAtomo868: + generated_protocol = subghz_txrx_gen_came_atomo_protocol( + subghz->txrx, "AM650", 868350000, (key & 0x0FFFFFFF) | 0x10000000, 0x0003); + break; case SubmenuIndexBFTMitto: generated_protocol = subghz_txrx_gen_keeloq_bft_protocol( subghz->txrx, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 7d77d7de8..9016f9274 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -2757,6 +2757,7 @@ Function,+,subghz_protocol_blocks_parity_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_reverse_key,uint64_t,"uint64_t, uint8_t" Function,+,subghz_protocol_blocks_set_bit_array,void,"_Bool, uint8_t[], size_t, size_t" Function,+,subghz_protocol_blocks_xor_bytes,uint8_t,"const uint8_t[], size_t" +Function,-,subghz_protocol_came_atomo_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint16_t, SubGhzRadioPreset*" Function,-,subghz_protocol_decoder_alutech_at_4n_alloc,void*,SubGhzEnvironment* Function,-,subghz_protocol_decoder_alutech_at_4n_deserialize,SubGhzProtocolStatus,"void*, FlipperFormat*" Function,-,subghz_protocol_decoder_alutech_at_4n_feed,void,"void*, _Bool, uint32_t" diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 870afede3..18e0214bb 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -120,6 +120,46 @@ static LevelDuration return level_duration_make(data.level, data.duration); } +bool subghz_protocol_came_atomo_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint16_t cnt, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderCameAtomo* instance = context; + instance->generic.btn = 0x1; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->generic.cnt_2 = 0x7e; + instance->generic.data_count_bit = 62; + instance->generic.data_2 = + ((uint64_t)0x7e << 56 | (uint64_t)cnt << 40 | (uint64_t)serial << 8); + + uint8_t pack[8] = {}; + + pack[0] = (instance->generic.cnt_2); + pack[1] = (instance->generic.cnt >> 8); + pack[2] = (instance->generic.cnt & 0xFF); + pack[3] = ((instance->generic.data_2 >> 32) & 0xFF); + pack[4] = ((instance->generic.data_2 >> 24) & 0xFF); + pack[5] = ((instance->generic.data_2 >> 16) & 0xFF); + pack[6] = ((instance->generic.data_2 >> 8) & 0xFF); + pack[7] = (instance->generic.data_2 & 0xFF); + + atomo_encrypt(pack); + uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3]; + uint32_t lo = pack[4] << 24 | pack[5] << 16 | pack[6] << 8 | pack[7]; + instance->generic.data = (uint64_t)hi << 32 | lo; + + instance->generic.data ^= 0xFFFFFFFFFFFFFFFF; + instance->generic.data >>= 4; + instance->generic.data &= 0xFFFFFFFFFFFFFFF; + + return SubGhzProtocolStatusOk == + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + /** * Generating an upload from data. * @param instance Pointer to a SubGhzProtocolEncoderCameAtomo instance diff --git a/lib/subghz/protocols/came_atomo.h b/lib/subghz/protocols/came_atomo.h index c5e45a68d..8183877b0 100644 --- a/lib/subghz/protocols/came_atomo.h +++ b/lib/subghz/protocols/came_atomo.h @@ -14,6 +14,22 @@ void atomo_decrypt(uint8_t* buff); void atomo_encrypt(uint8_t* buff); +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderCameAtomo instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 24 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_came_atomo_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint16_t cnt, + SubGhzRadioPreset* preset); + /** * Allocate SubGhzProtocolEncoderCameAtomo. * @param environment Pointer to a SubGhzEnvironment instance From 174c00a8e7d64277454ef521244e0fe5297a5efb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 14:45:48 +0300 Subject: [PATCH 04/40] Atomo Custom buttons --- lib/subghz/protocols/came_atomo.c | 122 +++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 12 deletions(-) diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 18e0214bb..4723ec99f 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -7,6 +7,8 @@ #include "../blocks/generic.h" #include "../blocks/math.h" +#include "../blocks/custom_btn.h" + #define TAG "SubGhzProtocoCameAtomo" static const SubGhzBlockConst subghz_protocol_came_atomo_const = { @@ -71,6 +73,13 @@ const SubGhzProtocol subghz_protocol_came_atomo = { static void subghz_protocol_came_atomo_remote_controller(SubGhzBlockGeneric* instance); +/** + * Defines the button value for the current btn_id + * Basic set | 0x0 | 0x2 | 0x4 | 0x6 | + * @return Button code + */ +static uint8_t subghz_protocol_came_atomo_get_btn_code(); + void* subghz_protocol_encoder_came_atomo_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolEncoderCameAtomo* instance = malloc(sizeof(SubGhzProtocolEncoderCameAtomo)); @@ -164,8 +173,9 @@ bool subghz_protocol_came_atomo_create_data( * Generating an upload from data. * @param instance Pointer to a SubGhzProtocolEncoderCameAtomo instance */ -static void - subghz_protocol_encoder_came_atomo_get_upload(SubGhzProtocolEncoderCameAtomo* instance) { +static void subghz_protocol_encoder_came_atomo_get_upload( + SubGhzProtocolEncoderCameAtomo* instance, + uint8_t btn) { furi_assert(instance); size_t index = 0; @@ -185,6 +195,23 @@ static void instance->generic.cnt = 0; } + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(btn); + } + + btn = subghz_protocol_came_atomo_get_btn_code(); + + if(btn == 0x1) { + btn = 0x0; + } else if(btn == 0x2) { + btn = 0x2; + } else if(btn == 0x3) { + btn = 0x4; + } else if(btn == 0x4) { + btn = 0x6; + } + //Send header instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_came_atomo_const.te_long * 15); @@ -199,7 +226,7 @@ static void pack[4] = ((instance->generic.data_2 >> 24) & 0xFF); pack[5] = ((instance->generic.data_2 >> 16) & 0xFF); pack[6] = ((instance->generic.data_2 >> 8) & 0xFF); - pack[7] = (instance->generic.data_2 & 0xFF); + pack[7] = (btn << 4); if(pack[0] == 0x7F) { pack[0] = 0; @@ -251,7 +278,7 @@ static void pack[4] = ((instance->generic.data_2 >> 24) & 0xFF); pack[5] = ((instance->generic.data_2 >> 16) & 0xFF); pack[6] = ((instance->generic.data_2 >> 8) & 0xFF); - pack[7] = (instance->generic.data_2 & 0xFF); + pack[7] = (btn << 4); atomo_encrypt(pack); uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3]; @@ -280,7 +307,7 @@ SubGhzProtocolStatus flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); subghz_protocol_came_atomo_remote_controller(&instance->generic); - subghz_protocol_encoder_came_atomo_get_upload(instance); + subghz_protocol_encoder_came_atomo_get_upload(instance, instance->generic.btn); if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); @@ -527,20 +554,23 @@ static void subghz_protocol_came_atomo_remote_controller(SubGhzBlockGeneric* ins uint8_t btn_decode = (pack[7] >> 4); if(btn_decode == 0x0) { instance->btn = 0x1; - } - if(btn_decode == 0x2) { + } else if(btn_decode == 0x2) { instance->btn = 0x2; - } - if(btn_decode == 0x4) { + } else if(btn_decode == 0x4) { instance->btn = 0x3; - } - if(btn_decode == 0x6) { + } else if(btn_decode == 0x6) { instance->btn = 0x4; } uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3]; uint32_t lo = pack[4] << 24 | pack[5] << 16 | pack[6] << 8 | pack[7]; instance->data_2 = (uint64_t)hi << 32 | lo; + + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(instance->btn); + } + subghz_custom_btn_set_max(3); } void atomo_encrypt(uint8_t* buff) { @@ -584,6 +614,74 @@ void atomo_decrypt(uint8_t* buff) { } } +static uint8_t subghz_protocol_came_atomo_get_btn_code() { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + // Set custom button + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x1: + btn = 0x2; + break; + case 0x2: + btn = 0x1; + break; + case 0x3: + btn = 0x1; + break; + case 0x4: + btn = 0x1; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x1: + btn = 0x3; + break; + case 0x2: + btn = 0x3; + break; + case 0x3: + btn = 0x2; + break; + case 0x4: + btn = 0x2; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x1: + btn = 0x4; + break; + case 0x2: + btn = 0x4; + break; + case 0x3: + btn = 0x4; + break; + case 0x4: + btn = 0x3; + break; + + default: + break; + } + } + + return btn; +} + uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderCameAtomo* instance = context; @@ -621,7 +719,7 @@ void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* ou output, "%s %db\r\n" "Key:0x%08lX%08lX\r\n" - "Sn:0x%08lX Btn:0x%01X\r\n" + "Sn:0x%08lX Btn:%01X\r\n" "Pcl_Cnt:0x%04lX\r\n" "Btn_Cnt:0x%02X", From e0cb823ed084fa098c53bb6840fc53da4daab457 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 14:46:41 +0300 Subject: [PATCH 05/40] what happened here, ctrl+c / ctrl+v? --- lib/subghz/protocols/dooya.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/subghz/protocols/dooya.c b/lib/subghz/protocols/dooya.c index 47e95209e..816847840 100644 --- a/lib/subghz/protocols/dooya.c +++ b/lib/subghz/protocols/dooya.c @@ -308,7 +308,7 @@ void subghz_protocol_decoder_dooya_feed(void* context, bool level, uint32_t dura * Analysis of received data * @param instance Pointer to a SubGhzBlockGeneric* instance */ -static void subghz_protocol_somfy_telis_check_remote_controller(SubGhzBlockGeneric* instance) { +static void subghz_protocol_dooya_check_remote_controller(SubGhzBlockGeneric* instance) { /* * serial s/m ch key * long press down X * E1DC030533, 40b 111000011101110000000011 0000 0101 0011 0011 @@ -416,7 +416,7 @@ void subghz_protocol_decoder_dooya_get_string(void* context, FuriString* output) furi_assert(context); SubGhzProtocolDecoderDooya* instance = context; - subghz_protocol_somfy_telis_check_remote_controller(&instance->generic); + subghz_protocol_dooya_check_remote_controller(&instance->generic); furi_string_cat_printf( output, From 9069bb6f6e83046e6731fc83a952c1f6fadc2c8f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 14:59:51 +0300 Subject: [PATCH 06/40] Update changelog and readme --- CHANGELOG.md | 5 ++++- ReadMe.md | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85d918843..ce55ad74b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ * Plugins: WiFi Marauder -> Added sniff pmkid on selected aps from 0.10.4 update (by @clipboard1) * Plugins: SubGHz Bruteforcer -> Increase delay just a little bit to fix some cases when receiver will not get codes and decrease manual transmit delay * Plugins: UART Terminal -> Fix crashes on plugin load with RX connected -* SubGHz: App refactoring (OFW code ported + our code was refactored/cleaned up too) (by @gid9798 and @xMasterX | PR #461) +* SubGHz: **CAME Atomo - Add manually support and custom buttons support** +* SubGHz: **App refactoring** (OFW code ported + our code was refactored/cleaned up too) (by @gid9798 and @xMasterX | PR #461) * SubGHz: Using scene manager functions in DecodeRAW (by @gid9798 | PR #462) * SubGHz: Protocols and custom buttons refactoring (by @gid9798 | PR #465) * SubGHz: Move `counter increase` setting out of debug, change max value @@ -14,6 +15,8 @@ * BT API: Functions that allow to change bt mac address and device broadcasted name (by @Willy-JL and XFW contributors) * Infrared: `External output` move out of debug and add power option for external modules * Extra pack: Some app fixes +* FBT: Fix vscode example config for debug builds - please run `./fbt vscode_dist` again if you had issues with debug builds size +* OFW: api: added lib/nfc/protocols/nfc_util.h * OFW: fix PIN retry count reset on reboot * OFW: fbt: allow strings for fap_version field in app manifests * OFW: Rpc: add desktop service. Desktop: refactor locking routine. **Now PIN lock is actually cannot be bypassed by reboot!** / **Desktop settings will be reset, please set your PIN and favourite apps again!** diff --git a/ReadMe.md b/ReadMe.md index 350db11d6..596891d6e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -59,8 +59,8 @@ Our Discord Community: * SubGHz -> Read mode UI improvements (scrolling text, + shows time when signal was received) (by @wosk) * Sub-GHz -> External CC1101 module support (Hardware SPI used) * SubGHz -> **Hold right in received signal list to delete selected signal** -* SubGHz -> **Custom buttons for Keeloq / Alutech AT4N / Nice Flor S / Somfy Telis / Security+ 2.0** - now you can use arrow buttons to send signal with different button code -* SubGHz -> BFT Mitto / Somfy Telis / Nice Flor S manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis) +* SubGHz -> **Custom buttons for Keeloq / Alutech AT4N / Nice Flor S / Somfy Telis / Security+ 2.0 / CAME Atomo** - now you can use arrow buttons to send signal with different button code +* SubGHz -> BFT Mitto / Somfy Telis / Nice Flor S / CAME Atomo, etc.. manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis) * SubGHz -> Debug mode counter increase settings (+1 -> +5, +10, default: +1) * SubGHz -> Debug PIN output settings for protocol development * Infrared -> Debug TX PIN output settings @@ -76,6 +76,7 @@ Keeloq [Not ALL systems supported for decode or emulation yet!] - [Supported man Encoders or sending made by @xMasterX: - Nero Radio 57bit (+ 56bit encoder improvements) +- CAME 12bit/24bit encoder fixes (already merged in OFW) - Keeloq: HCS101 - Keeloq: AN-Motors - Keeloq: JCM Tech @@ -90,20 +91,20 @@ Encoders or sending made by @xMasterX: - Keeloq: CAME Space - Keeloq: Aprimatic (model TR and similar) -Encoders/sending made by @Eng1n33r & @xMasterX: -- CAME Atomo -- Nice Flor S +Encoders or sending made by @Eng1n33r(first implementation in Q2 2022) & @xMasterX (current version): +- CAME Atomo -> Update! check out new [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) +- Nice Flor S -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) - FAAC SLH (Spa) [External seed calculation required (For info contact me in Discord: Nano#8998)] - Keeloq: BFT Mitto [External seed calculation required (For info contact me in Discord: Nano#8998)] -> Update! check out new [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) - Star Line -- Security+ v1 & v2 +- Security+ v1 & v2 (encoders was made in OFW) Encoders made by @assasinfil & @xMasterX: -- Somfy Telis +- Somfy Telis -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) - Somfy Keytis - KingGates Stylo 4k -- Alutech AT-4N -- Nice ON2E (Nice One) +- Alutech AT-4N -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) +- Nice ON2E (Nice One) -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) ## Please support development of the project The majority of this project is developed and maintained by me, @xMasterX. From 90f18075cb28b486ea6f1d53c967963709a840cd Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 15:41:51 +0300 Subject: [PATCH 07/40] Restore Rx indication after deletion after Memory is FULL by @wosk / PR #464 --- applications/main/subghz/scenes/subghz_scene_receiver.c | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 9987449cd..273105bfe 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -227,6 +227,7 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case SubGhzCustomEventViewReceiverDeleteItem: + subghz->state_notifications = SubGhzNotificationStateRx; subghz->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); subghz_history_delete_item(subghz->history, subghz->idx_menu_chosen); From 88e47e9251c0c8e801e3e7858ebd3fce47138a00 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 17:24:14 +0300 Subject: [PATCH 08/40] Fix crashes when deleting signals in Read mode --- .../subghz/scenes/subghz_scene_receiver.c | 4 + applications/main/subghz/views/receiver.c | 122 +++++++++++------- applications/main/subghz/views/receiver.h | 4 + 3 files changed, 81 insertions(+), 49 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 273105bfe..943d47943 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -230,9 +230,13 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { subghz->state_notifications = SubGhzNotificationStateRx; subghz->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); + subghz_view_receiver_disable_draw_callback(subghz->subghz_receiver); + subghz_history_delete_item(subghz->history, subghz->idx_menu_chosen); subghz_view_receiver_delete_element_callback(subghz->subghz_receiver); + subghz_view_receiver_enable_draw_callback(subghz->subghz_receiver); + subghz_scene_receiver_update_statusbar(subghz); consumed = true; break; diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index 6f272b6a6..53921f866 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -71,6 +71,7 @@ typedef struct { SubGhzViewReceiverMode mode; uint8_t u_rssi; size_t scroll_counter; + bool nodraw; } SubGhzViewReceiverModel; void subghz_view_receiver_set_mode( @@ -241,39 +242,47 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { bool scrollbar = model->history_item > 4; FuriString* str_buff = furi_string_alloc(); - SubGhzReceiverMenuItem* item_menu; + if(!model->nodraw) { + SubGhzReceiverMenuItem* item_menu; - for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { - size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0); - item_menu = SubGhzReceiverMenuItemArray_get(model->history->data, idx); - furi_string_set(str_buff, item_menu->item_str); - size_t scroll_counter = model->scroll_counter; - if(model->idx == idx) { - subghz_view_receiver_draw_frame(canvas, i, scrollbar); - if(scroll_counter < SCROLL_DELAY) { - // Show time of signal one moment - furi_string_set(str_buff, item_menu->time); - scroll_counter = 0; - } else { - scroll_counter -= SCROLL_DELAY; + for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { + size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0); + item_menu = SubGhzReceiverMenuItemArray_get(model->history->data, idx); + if(item_menu == NULL) { + break; } - } else { - canvas_set_color(canvas, ColorBlack); - scroll_counter = 0; + if(item_menu->type == 0) { + break; + } + furi_string_set(str_buff, item_menu->item_str); + size_t scroll_counter = model->scroll_counter; + if(model->idx == idx) { + subghz_view_receiver_draw_frame(canvas, i, scrollbar); + if(scroll_counter < SCROLL_DELAY) { + // Show time of signal one moment + furi_string_set(str_buff, item_menu->time); + scroll_counter = 0; + } else { + scroll_counter -= SCROLL_DELAY; + } + } else { + canvas_set_color(canvas, ColorBlack); + scroll_counter = 0; + } + canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); + elements_scrollable_text_line( + canvas, + 15, + 9 + i * FRAME_HEIGHT, + (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX), + str_buff, + scroll_counter, + (model->idx != idx)); + furi_string_reset(str_buff); + } + if(scrollbar) { + elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); } - canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); - elements_scrollable_text_line( - canvas, - 15, - 9 + i * FRAME_HEIGHT, - (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX), - str_buff, - scroll_counter, - (model->idx != idx)); - furi_string_reset(str_buff); - } - if(scrollbar) { - elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); } furi_string_free(str_buff); @@ -464,29 +473,12 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { SubGhzViewReceiverModel * model, { if(model->history_item != 0) { - SubGhzReceiverMenuItemArray_it_t it; - // SubGhzReceiverMenuItem* target_item = - // SubGhzReceiverMenuItemArray_get(model->history->data, model->idx); - SubGhzReceiverMenuItemArray_it_last(it, model->history->data); - while(!SubGhzReceiverMenuItemArray_end_p(it)) { - SubGhzReceiverMenuItem* item = SubGhzReceiverMenuItemArray_ref(it); - - if(it->index == (size_t)(model->idx)) { - furi_string_free(item->item_str); - furi_string_free(item->time); - item->type = 0; - SubGhzReceiverMenuItemArray_remove(model->history->data, it); - } - - SubGhzReceiverMenuItemArray_previous(it); - } - // Callback subghz_receiver->callback( SubGhzCustomEventViewReceiverDeleteItem, subghz_receiver->context); } }, - true); + false); } else if(event->key == InputKeyOk && event->type == InputTypeShort) { with_view_model( subghz_receiver->view, @@ -537,6 +529,7 @@ void subghz_view_receiver_exit(void* context) { model->idx = 0; model->list_offset = 0; model->history_item = 0; + model->nodraw = false; }, false); furi_timer_stop(subghz_receiver->timer); @@ -571,6 +564,7 @@ SubGhzViewReceiver* subghz_view_receiver_alloc() { model->history_stat_str = furi_string_alloc(); model->progress_str = furi_string_alloc(); model->bar_show = SubGhzViewReceiverBarShowDefault; + model->nodraw = false; model->history = malloc(sizeof(SubGhzReceiverHistory)); SubGhzReceiverMenuItemArray_init(model->history->data); }, @@ -628,6 +622,23 @@ void subghz_view_receiver_delete_element_callback(SubGhzViewReceiver* subghz_rec subghz_receiver->view, SubGhzViewReceiverModel * model, { + SubGhzReceiverMenuItemArray_it_t it; + // SubGhzReceiverMenuItem* target_item = + // SubGhzReceiverMenuItemArray_get(model->history->data, model->idx); + SubGhzReceiverMenuItemArray_it_last(it, model->history->data); + while(!SubGhzReceiverMenuItemArray_end_p(it)) { + SubGhzReceiverMenuItem* item = SubGhzReceiverMenuItemArray_ref(it); + + if(it->index == (size_t)(model->idx)) { + furi_string_free(item->item_str); + furi_string_free(item->time); + item->type = 0; + SubGhzReceiverMenuItemArray_remove(model->history->data, it); + } + + SubGhzReceiverMenuItemArray_previous(it); + } + if(model->history_item == 5) { if(model->idx >= 2) { model->idx = model->history_item - 1; @@ -640,7 +651,20 @@ void subghz_view_receiver_delete_element_callback(SubGhzViewReceiver* subghz_rec } }, true); - furi_delay_ms(200); +} + +void subghz_view_receiver_enable_draw_callback(SubGhzViewReceiver* subghz_receiver) { + furi_assert(subghz_receiver); + + with_view_model( + subghz_receiver->view, SubGhzViewReceiverModel * model, { model->nodraw = false; }, true); +} + +void subghz_view_receiver_disable_draw_callback(SubGhzViewReceiver* subghz_receiver) { + furi_assert(subghz_receiver); + + with_view_model( + subghz_receiver->view, SubGhzViewReceiverModel * model, { model->nodraw = true; }, true); } void subghz_view_receiver_set_idx_menu(SubGhzViewReceiver* subghz_receiver, uint16_t idx) { diff --git a/applications/main/subghz/views/receiver.h b/applications/main/subghz/views/receiver.h index 54c1b3e7d..f239331d5 100644 --- a/applications/main/subghz/views/receiver.h +++ b/applications/main/subghz/views/receiver.h @@ -49,4 +49,8 @@ void subghz_view_receiver_set_idx_menu(SubGhzViewReceiver* subghz_receiver, uint void subghz_view_receiver_delete_element_callback(SubGhzViewReceiver* subghz_receiver); +void subghz_view_receiver_enable_draw_callback(SubGhzViewReceiver* subghz_receiver); + +void subghz_view_receiver_disable_draw_callback(SubGhzViewReceiver* subghz_receiver); + void subghz_view_receiver_exit(void* context); From e011c844de13b50aabf9fe2c3891b1492a9acade Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 17:29:08 +0300 Subject: [PATCH 09/40] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce55ad74b..a2f1a2a28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ * Plugins: SubGHz Bruteforcer -> Increase delay just a little bit to fix some cases when receiver will not get codes and decrease manual transmit delay * Plugins: UART Terminal -> Fix crashes on plugin load with RX connected * SubGHz: **CAME Atomo - Add manually support and custom buttons support** +* SubGHz: Fix crashes when deleting signals using right arrow button in `Read` mode +* SubGHz: Restore Rx indication after deletion after Memory is FULL (by @wosk | PR #464) * SubGHz: **App refactoring** (OFW code ported + our code was refactored/cleaned up too) (by @gid9798 and @xMasterX | PR #461) * SubGHz: Using scene manager functions in DecodeRAW (by @gid9798 | PR #462) * SubGHz: Protocols and custom buttons refactoring (by @gid9798 | PR #465) From 45ae0c097b5dfeaf5b77a24863c556fc39af1cbd Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 17:51:25 +0300 Subject: [PATCH 10/40] Fix need saving message when all signals was removed --- applications/main/subghz/scenes/subghz_scene_receiver.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 943d47943..26e92e983 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -238,6 +238,9 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { subghz_view_receiver_enable_draw_callback(subghz->subghz_receiver); subghz_scene_receiver_update_statusbar(subghz); + if(subghz_history_get_last_index(subghz->history) == 0) { + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateStart); + } consumed = true; break; case SubGhzCustomEventViewReceiverConfig: From c760bca4a9508f86dcf5855499572807faa685c9 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 19:45:30 +0300 Subject: [PATCH 11/40] OFW PR 2316: NFC V support https://github.com/g3gg0/flipperzero-firmware/tree/ISO15693 --- .../main/nfc/helpers/nfc_custom_event.h | 2 + applications/main/nfc/nfc.c | 3 + .../main/nfc/scenes/nfc_scene_config.h | 6 + .../main/nfc/scenes/nfc_scene_delete.c | 7 +- .../main/nfc/scenes/nfc_scene_extra_actions.c | 20 + .../main/nfc/scenes/nfc_scene_nfc_data_info.c | 175 ++- .../main/nfc/scenes/nfc_scene_nfcv_emulate.c | 164 ++ .../nfc/scenes/nfc_scene_nfcv_key_input.c | 48 + .../main/nfc/scenes/nfc_scene_nfcv_menu.c | 63 + .../main/nfc/scenes/nfc_scene_nfcv_sniff.c | 155 ++ .../main/nfc/scenes/nfc_scene_nfcv_unlock.c | 154 ++ .../nfc/scenes/nfc_scene_nfcv_unlock_menu.c | 60 + applications/main/nfc/scenes/nfc_scene_read.c | 5 + applications/main/nfc/scenes/nfc_scene_rpc.c | 7 + .../main/nfc/scenes/nfc_scene_saved_menu.c | 3 + firmware/targets/f7/api_symbols.csv | 8 + lib/nfc/nfc_device.c | 356 ++++- lib/nfc/nfc_device.h | 4 + lib/nfc/nfc_worker.c | 248 ++- lib/nfc/nfc_worker.h | 11 + lib/nfc/nfc_worker_i.h | 2 + lib/nfc/protocols/nfcv.c | 1393 +++++++++++++++++ lib/nfc/protocols/nfcv.h | 291 ++++ lib/nfc/protocols/slix.c | 412 +++++ lib/nfc/protocols/slix.h | 46 + 25 files changed, 3613 insertions(+), 30 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_menu.c create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c create mode 100644 lib/nfc/protocols/nfcv.c create mode 100644 lib/nfc/protocols/nfcv.h create mode 100644 lib/nfc/protocols/slix.c create mode 100644 lib/nfc/protocols/slix.h diff --git a/applications/main/nfc/helpers/nfc_custom_event.h b/applications/main/nfc/helpers/nfc_custom_event.h index 4227a5b14..aa932a3d8 100644 --- a/applications/main/nfc/helpers/nfc_custom_event.h +++ b/applications/main/nfc/helpers/nfc_custom_event.h @@ -12,4 +12,6 @@ enum NfcCustomEvent { NfcCustomEventDictAttackSkip, NfcCustomEventRpcLoad, NfcCustomEventRpcSessionClose, + NfcCustomEventUpdateLog, + NfcCustomEventSaveShadow, }; diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 16d5837ea..59110940c 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -290,6 +290,9 @@ int32_t nfc_app(void* p) { } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); DOLPHIN_DEED(DolphinDeedNfcEmulate); + } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); + DOLPHIN_DEED(DolphinDeedNfcEmulate); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); DOLPHIN_DEED(DolphinDeedNfcEmulate); diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index a9da07dfd..d6edebe73 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -14,6 +14,12 @@ ADD_SCENE(nfc, file_select, FileSelect) ADD_SCENE(nfc, emulate_uid, EmulateUid) ADD_SCENE(nfc, nfca_read_success, NfcaReadSuccess) ADD_SCENE(nfc, nfca_menu, NfcaMenu) +ADD_SCENE(nfc, nfcv_menu, NfcVMenu) +ADD_SCENE(nfc, nfcv_unlock_menu, NfcVUnlockMenu) +ADD_SCENE(nfc, nfcv_key_input, NfcVKeyInput) +ADD_SCENE(nfc, nfcv_unlock, NfcVUnlock) +ADD_SCENE(nfc, nfcv_emulate, NfcVEmulate) +ADD_SCENE(nfc, nfcv_sniff, NfcVSniff) ADD_SCENE(nfc, mf_ultralight_read_success, MfUltralightReadSuccess) ADD_SCENE(nfc, mf_ultralight_data, MfUltralightData) ADD_SCENE(nfc, mf_ultralight_menu, MfUltralightMenu) diff --git a/applications/main/nfc/scenes/nfc_scene_delete.c b/applications/main/nfc/scenes/nfc_scene_delete.c index cbb52bfd0..0808db45a 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete.c +++ b/applications/main/nfc/scenes/nfc_scene_delete.c @@ -31,6 +31,8 @@ void nfc_scene_delete_on_enter(void* context) { nfc->widget, 64, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); NfcProtocol protocol = nfc->dev->dev_data.protocol; + const char* nfc_type = "NFC-A"; + if(protocol == NfcDeviceProtocolEMV) { furi_string_set(temp_str, "EMV bank card"); } else if(protocol == NfcDeviceProtocolMifareUl) { @@ -39,12 +41,15 @@ void nfc_scene_delete_on_enter(void* context) { furi_string_set(temp_str, nfc_mf_classic_type(nfc->dev->dev_data.mf_classic_data.type)); } else if(protocol == NfcDeviceProtocolMifareDesfire) { furi_string_set(temp_str, "MIFARE DESFire"); + } else if(protocol == NfcDeviceProtocolNfcV) { + furi_string_set(temp_str, "ISO15693 tag"); + nfc_type = "NFC-V"; } else { furi_string_set(temp_str, "Unknown ISO tag"); } widget_add_string_element( nfc->widget, 64, 34, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - widget_add_string_element(nfc->widget, 64, 44, AlignCenter, AlignTop, FontSecondary, "NFC-A"); + widget_add_string_element(nfc->widget, 64, 44, AlignCenter, AlignTop, FontSecondary, nfc_type); furi_string_free(temp_str); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 66aaf5a26..7f5bc7e75 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -4,6 +4,8 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, SubmenuIndexMfUltralightUnlock, + SubmenuIndexNfcVUnlock, + SubmenuIndexNfcVSniff, }; void nfc_scene_extra_actions_submenu_callback(void* context, uint32_t index) { @@ -34,6 +36,18 @@ void nfc_scene_extra_actions_on_enter(void* context) { SubmenuIndexMfUltralightUnlock, nfc_scene_extra_actions_submenu_callback, nfc); + submenu_add_item( + submenu, + "Unlock SLIX-L", + SubmenuIndexNfcVUnlock, + nfc_scene_extra_actions_submenu_callback, + nfc); + submenu_add_item( + submenu, + "Listen NfcV Reader", + SubmenuIndexNfcVSniff, + nfc_scene_extra_actions_submenu_callback, + nfc); submenu_set_selected_item( submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneExtraActions)); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); @@ -58,6 +72,12 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state(nfc->scene_manager, NfcSceneReadCardType, 0); scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardType); consumed = true; + } else if(event.event == SubmenuIndexNfcVUnlock) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlockMenu); + consumed = true; + } else if(event.event == SubmenuIndexNfcVSniff) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVSniff); + consumed = true; } scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event); } diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index 92ad7b56e..38f3e75c7 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -7,6 +7,17 @@ void nfc_scene_nfc_data_info_widget_callback(GuiButtonType result, InputType typ } } +uint32_t nfc_scene_nfc_data_info_get_key(uint8_t* data) { + uint32_t value = 0; + + for(uint32_t pos = 0; pos < 4; pos++) { + value <<= 8; + value |= data[pos]; + } + + return value; +} + void nfc_scene_nfc_data_info_on_enter(void* context) { Nfc* nfc = context; Widget* widget = nfc->widget; @@ -15,7 +26,7 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { NfcProtocol protocol = dev_data->protocol; uint8_t text_scroll_height = 0; if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl) || - (protocol == NfcDeviceProtocolMifareClassic)) { + (protocol == NfcDeviceProtocolMifareClassic) || (protocol == NfcDeviceProtocolNfcV)) { widget_add_button_element( widget, GuiButtonTypeRight, "More", nfc_scene_nfc_data_info_widget_callback, nfc); text_scroll_height = 52; @@ -41,19 +52,165 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { temp_str, "\e#%s\n", nfc_mf_classic_type(dev_data->mf_classic_data.type)); } else if(protocol == NfcDeviceProtocolMifareDesfire) { furi_string_cat_printf(temp_str, "\e#MIFARE DESfire\n"); + } else if(protocol == NfcDeviceProtocolNfcV) { + switch(dev_data->nfcv_data.sub_type) { + case NfcVTypePlain: + furi_string_cat_printf(temp_str, "\e#ISO15693\n"); + break; + case NfcVTypeSlix: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX\n"); + break; + case NfcVTypeSlixS: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-S\n"); + break; + case NfcVTypeSlixL: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-L\n"); + break; + case NfcVTypeSlix2: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX2\n"); + break; + default: + furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); + break; + } } else { furi_string_cat_printf(temp_str, "\e#Unknown ISO tag\n"); } // Set tag iso data - char iso_type = FURI_BIT(nfc_data->sak, 5) ? '4' : '3'; - furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + if(protocol == NfcDeviceProtocolNfcV) { + NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; + + furi_string_cat_printf(temp_str, "UID:\n"); + for(size_t i = 0; i < nfc_data->uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + } + furi_string_cat_printf(temp_str, "\n"); + + furi_string_cat_printf( + temp_str, + "DSFID: %02X %s\n", + nfcv_data->dsfid, + (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? "(locked)" : ""); + furi_string_cat_printf( + temp_str, + "AFI: %02X %s\n", + nfcv_data->afi, + (nfcv_data->security_status[0] & NfcVLockBitAfi) ? "(locked)" : ""); + furi_string_cat_printf(temp_str, "IC Ref: %02X\n", nfcv_data->ic_ref); + furi_string_cat_printf(temp_str, "Blocks: %02X\n", nfcv_data->block_num); + furi_string_cat_printf(temp_str, "Blocksize: %02X\n", nfcv_data->block_size); + + furi_string_cat_printf( + temp_str, "Data (%d byte)\n", nfcv_data->block_num * nfcv_data->block_size); + + int maxBlocks = nfcv_data->block_num; + if(maxBlocks > 32) { + maxBlocks = 32; + furi_string_cat_printf(temp_str, "(truncated to %d blocks)\n", maxBlocks); + } + + for(int block = 0; block < maxBlocks; block++) { + const char* status = (nfcv_data->security_status[block] & 0x01) ? "(lck)" : ""; + for(int pos = 0; pos < nfcv_data->block_size; pos++) { + furi_string_cat_printf( + temp_str, " %02X", nfcv_data->data[block * nfcv_data->block_size + pos]); + } + furi_string_cat_printf(temp_str, " %s\n", status); + } + furi_string_cat_printf(temp_str, "\n"); + + switch(dev_data->nfcv_data.sub_type) { + case NfcVTypePlain: + furi_string_cat_printf(temp_str, "Type: Plain\n"); + break; + case NfcVTypeSlix: + furi_string_cat_printf(temp_str, "Type: SLIX\n"); + furi_string_cat_printf(temp_str, "Keys:\n"); + furi_string_cat_printf( + temp_str, + " EAS %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_eas)); + break; + case NfcVTypeSlixS: + furi_string_cat_printf(temp_str, "Type: SLIX-S\n"); + furi_string_cat_printf(temp_str, "Keys:\n"); + furi_string_cat_printf( + temp_str, + " Read %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_read)); + furi_string_cat_printf( + temp_str, + " Write %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_write)); + furi_string_cat_printf( + temp_str, + " Privacy %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_privacy)); + furi_string_cat_printf( + temp_str, + " Destroy %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_destroy)); + furi_string_cat_printf( + temp_str, + " EAS %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_eas)); + break; + case NfcVTypeSlixL: + furi_string_cat_printf(temp_str, "Type: SLIX-L\n"); + furi_string_cat_printf(temp_str, "Keys:\n"); + furi_string_cat_printf( + temp_str, + " Privacy %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_privacy)); + furi_string_cat_printf( + temp_str, + " Destroy %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_destroy)); + furi_string_cat_printf( + temp_str, + " EAS %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_eas)); + break; + case NfcVTypeSlix2: + furi_string_cat_printf(temp_str, "Type: SLIX2\n"); + furi_string_cat_printf(temp_str, "Keys:\n"); + furi_string_cat_printf( + temp_str, + " Read %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_read)); + furi_string_cat_printf( + temp_str, + " Write %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_write)); + furi_string_cat_printf( + temp_str, + " Privacy %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_privacy)); + furi_string_cat_printf( + temp_str, + " Destroy %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_destroy)); + furi_string_cat_printf( + temp_str, + " EAS %08lX\n", + nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_eas)); + break; + default: + furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); + break; + } + } else { + char iso_type = FURI_BIT(nfc_data->sak, 5) ? '4' : '3'; + furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); + furi_string_cat_printf(temp_str, "UID:"); + for(size_t i = 0; i < nfc_data->uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + } + furi_string_cat_printf( + temp_str, "\nATQA: %02X %02X ", nfc_data->atqa[1], nfc_data->atqa[0]); + furi_string_cat_printf(temp_str, " SAK: %02X", nfc_data->sak); } - furi_string_cat_printf(temp_str, "\nATQA: %02X %02X ", nfc_data->atqa[1], nfc_data->atqa[0]); - furi_string_cat_printf(temp_str, " SAK: %02X", nfc_data->sak); // Set application specific data if(protocol == NfcDeviceProtocolMifareDesfire) { @@ -139,6 +296,8 @@ bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) { consumed = true; } else if(protocol == NfcDeviceProtocolMifareClassic) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicData); + } else if(protocol == NfcDeviceProtocolNfcV) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVMenu); consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c b/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c new file mode 100644 index 000000000..ca10f5d6e --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c @@ -0,0 +1,164 @@ +#include "../nfc_i.h" + +#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (200) + +enum { + NfcSceneNfcVEmulateStateWidget, + NfcSceneNfcVEmulateStateTextBox, +}; + +bool nfc_scene_nfcv_emulate_worker_callback(NfcWorkerEvent event, void* context) { + UNUSED(event); + furi_assert(context); + Nfc* nfc = context; + + switch(event) { + case NfcWorkerEventNfcVCommandExecuted: + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventUpdateLog); + break; + case NfcWorkerEventNfcVContentChanged: + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventSaveShadow); + break; + default: + break; + } + return true; +} + +void nfc_scene_nfcv_emulate_widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + Nfc* nfc = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + } +} + +void nfc_scene_nfcv_emulate_textbox_callback(void* context) { + furi_assert(context); + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +static void nfc_scene_nfcv_emulate_widget_config(Nfc* nfc, bool data_received) { + FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; + Widget* widget = nfc->widget; + widget_reset(widget); + FuriString* info_str; + info_str = furi_string_alloc(); + + widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61); + widget_add_string_element( + widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating NfcV"); + if(strcmp(nfc->dev->dev_name, "")) { + furi_string_printf(info_str, "%s", nfc->dev->dev_name); + } else { + for(uint8_t i = 0; i < data->uid_len; i++) { + furi_string_cat_printf(info_str, "%02X ", data->uid[i]); + } + } + furi_string_trim(info_str); + widget_add_text_box_element( + widget, 56, 43, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); + furi_string_free(info_str); + if(data_received) { + widget_add_button_element( + widget, GuiButtonTypeCenter, "Log", nfc_scene_nfcv_emulate_widget_callback, nfc); + } +} + +void nfc_scene_nfcv_emulate_on_enter(void* context) { + Nfc* nfc = context; + + // Setup Widget + nfc_scene_nfcv_emulate_widget_config(nfc, false); + // Setup TextBox + TextBox* text_box = nfc->text_box; + text_box_set_font(text_box, TextBoxFontHex); + text_box_set_focus(text_box, TextBoxFocusEnd); + text_box_set_text(text_box, ""); + furi_string_reset(nfc->text_box_store); + + // Set Widget state and view + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + // Start worker + memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); + nfc_worker_start( + nfc->worker, + NfcWorkerStateNfcVEmulate, + &nfc->dev->dev_data, + nfc_scene_nfcv_emulate_worker_callback, + nfc); + + nfc_blink_emulate_start(nfc); +} + +bool nfc_scene_nfcv_emulate_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVEmulate); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventUpdateLog) { + // Add data button to widget if data is received for the first time + if(strlen(nfcv_data->last_command) > 0) { + if(!furi_string_size(nfc->text_box_store)) { + nfc_scene_nfcv_emulate_widget_config(nfc, true); + } + /* use the last n bytes from the log so there's enough space for the new log entry */ + size_t maxSize = + NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX - (strlen(nfcv_data->last_command) + 1); + if(furi_string_size(nfc->text_box_store) >= maxSize) { + furi_string_right(nfc->text_box_store, (strlen(nfcv_data->last_command) + 1)); + } + furi_string_cat_printf(nfc->text_box_store, "%s", nfcv_data->last_command); + furi_string_push_back(nfc->text_box_store, '\n'); + text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); + + /* clear previously logged command */ + strcpy(nfcv_data->last_command, ""); + } + consumed = true; + } else if(event.event == NfcCustomEventSaveShadow) { + if(furi_string_size(nfc->dev->load_path)) { + nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); + } + consumed = true; + } else if(event.event == GuiButtonTypeCenter && state == NfcSceneNfcVEmulateStateWidget) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateTextBox); + consumed = true; + } else if(event.event == NfcCustomEventViewExit && state == NfcSceneNfcVEmulateStateTextBox) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state == NfcSceneNfcVEmulateStateTextBox) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_nfcv_emulate_on_exit(void* context) { + Nfc* nfc = context; + + // Stop worker + nfc_worker_stop(nfc->worker); + + // Clear view + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + + nfc_blink_stop(nfc); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c b/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c new file mode 100644 index 000000000..cc53c4dcb --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c @@ -0,0 +1,48 @@ +#include "../nfc_i.h" +#include + +void nfc_scene_nfcv_key_input_byte_input_callback(void* context) { + Nfc* nfc = context; + NfcVSlixData* data = &nfc->dev->dev_data.nfcv_data.sub_data.slix; + + memcpy(data->key_privacy, nfc->byte_input_store, 4); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_nfcv_key_input_on_enter(void* context) { + Nfc* nfc = context; + + // Setup view + ByteInput* byte_input = nfc->byte_input; + byte_input_set_header_text(byte_input, "Enter The Password In Hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_nfcv_key_input_byte_input_callback, + NULL, + nfc, + nfc->byte_input_store, + 4); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_nfcv_key_input_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock); + DOLPHIN_DEED(DolphinDeedNfcRead); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_nfcv_key_input_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(nfc->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c b/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c new file mode 100644 index 000000000..44d677513 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c @@ -0,0 +1,63 @@ +#include "../nfc_i.h" +#include + +enum SubmenuIndex { + SubmenuIndexSave, + SubmenuIndexEmulate, +}; + +void nfc_scene_nfcv_menu_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_nfcv_menu_on_enter(void* context) { + Nfc* nfc = context; + Submenu* submenu = nfc->submenu; + + submenu_add_item( + submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_nfcv_menu_submenu_callback, nfc); + submenu_add_item(submenu, "Save", SubmenuIndexSave, nfc_scene_nfcv_menu_submenu_callback, nfc); + + submenu_set_selected_item( + nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVMenu)); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_nfcv_menu_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSave) { + nfc->dev->format = NfcDeviceSaveFormatNfcV; + // 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_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { + DOLPHIN_DEED(DolphinDeedNfcAddEmulate); + } else { + DOLPHIN_DEED(DolphinDeedNfcEmulate); + } + consumed = true; + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVMenu, event.event); + + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_previous_scene(nfc->scene_manager); + } + + return consumed; +} + +void nfc_scene_nfcv_menu_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c b/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c new file mode 100644 index 000000000..2c0f17981 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c @@ -0,0 +1,155 @@ +#include "../nfc_i.h" + +#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (800) + +enum { + NfcSceneNfcVSniffStateWidget, + NfcSceneNfcVSniffStateTextBox, +}; + +bool nfc_scene_nfcv_sniff_worker_callback(NfcWorkerEvent event, void* context) { + UNUSED(event); + furi_assert(context); + Nfc* nfc = context; + + switch(event) { + case NfcWorkerEventNfcVCommandExecuted: + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventUpdateLog); + break; + case NfcWorkerEventNfcVContentChanged: + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventSaveShadow); + break; + default: + break; + } + return true; +} + +void nfc_scene_nfcv_sniff_widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + Nfc* nfc = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + } +} + +void nfc_scene_nfcv_sniff_textbox_callback(void* context) { + furi_assert(context); + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +static void nfc_scene_nfcv_sniff_widget_config(Nfc* nfc, bool data_received) { + Widget* widget = nfc->widget; + widget_reset(widget); + FuriString* info_str; + info_str = furi_string_alloc(); + + widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61); + widget_add_string_element(widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Listen NfcV"); + furi_string_trim(info_str); + widget_add_text_box_element( + widget, 56, 43, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); + furi_string_free(info_str); + if(data_received) { + widget_add_button_element( + widget, GuiButtonTypeCenter, "Log", nfc_scene_nfcv_sniff_widget_callback, nfc); + } +} + +void nfc_scene_nfcv_sniff_on_enter(void* context) { + Nfc* nfc = context; + + // Setup Widget + nfc_scene_nfcv_sniff_widget_config(nfc, false); + // Setup TextBox + TextBox* text_box = nfc->text_box; + text_box_set_font(text_box, TextBoxFontHex); + text_box_set_focus(text_box, TextBoxFocusEnd); + text_box_set_text(text_box, ""); + furi_string_reset(nfc->text_box_store); + + // Set Widget state and view + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + // Start worker + memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); + nfc_worker_start( + nfc->worker, + NfcWorkerStateNfcVSniff, + &nfc->dev->dev_data, + nfc_scene_nfcv_sniff_worker_callback, + nfc); + + nfc_blink_emulate_start(nfc); +} + +bool nfc_scene_nfcv_sniff_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVSniff); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventUpdateLog) { + // Add data button to widget if data is received for the first time + if(strlen(nfcv_data->last_command) > 0) { + if(!furi_string_size(nfc->text_box_store)) { + nfc_scene_nfcv_sniff_widget_config(nfc, true); + } + /* use the last n bytes from the log so there's enough space for the new log entry */ + size_t maxSize = + NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX - (strlen(nfcv_data->last_command) + 1); + if(furi_string_size(nfc->text_box_store) >= maxSize) { + furi_string_right(nfc->text_box_store, (strlen(nfcv_data->last_command) + 1)); + } + furi_string_cat_printf(nfc->text_box_store, "%s", nfcv_data->last_command); + furi_string_push_back(nfc->text_box_store, '\n'); + text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); + + /* clear previously logged command */ + strcpy(nfcv_data->last_command, ""); + } + consumed = true; + } else if(event.event == NfcCustomEventSaveShadow) { + if(furi_string_size(nfc->dev->load_path)) { + nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); + } + consumed = true; + } else if(event.event == GuiButtonTypeCenter && state == NfcSceneNfcVSniffStateWidget) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateTextBox); + consumed = true; + } else if(event.event == NfcCustomEventViewExit && state == NfcSceneNfcVSniffStateTextBox) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state == NfcSceneNfcVSniffStateTextBox) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_nfcv_sniff_on_exit(void* context) { + Nfc* nfc = context; + + // Stop worker + nfc_worker_stop(nfc->worker); + + // Clear view + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + + nfc_blink_stop(nfc); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c new file mode 100644 index 000000000..26de304de --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c @@ -0,0 +1,154 @@ +#include "../nfc_i.h" +#include + +typedef enum { + NfcSceneNfcVUnlockStateIdle, + NfcSceneNfcVUnlockStateDetecting, + NfcSceneNfcVUnlockStateUnlocked, + NfcSceneNfcVUnlockStateAlreadyUnlocked, + NfcSceneNfcVUnlockStateNotSupportedCard, +} NfcSceneNfcVUnlockState; + +static bool nfc_scene_nfcv_unlock_worker_callback(NfcWorkerEvent event, void* context) { + Nfc* nfc = context; + NfcVSlixData* data = &nfc->dev->dev_data.nfcv_data.sub_data.slix; + + if(event == NfcWorkerEventNfcVPassKey) { + memcpy(data->key_privacy, nfc->byte_input_store, 4); + } else { + view_dispatcher_send_custom_event(nfc->view_dispatcher, event); + } + return true; +} + +void nfc_scene_nfcv_unlock_popup_callback(void* context) { + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_nfcv_unlock_set_state(Nfc* nfc, NfcSceneNfcVUnlockState state) { + FuriHalNfcDevData* nfc_data = &(nfc->dev->dev_data.nfc_data); + NfcVData* nfcv_data = &(nfc->dev->dev_data.nfcv_data); + + uint32_t curr_state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVUnlock); + if(curr_state != state) { + Popup* popup = nfc->popup; + if(state == NfcSceneNfcVUnlockStateDetecting) { + popup_reset(popup); + popup_set_text( + popup, "Put figurine on\nFlipper's back", 97, 24, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 8, &I_NFC_manual_60x50); + } else if(state == NfcSceneNfcVUnlockStateUnlocked) { + popup_reset(popup); + + if(nfc_worker_get_state(nfc->worker) == NfcWorkerStateNfcVUnlockAndSave) { + snprintf( + nfc->dev->dev_name, + sizeof(nfc->dev->dev_name), + "SLIX_%02X%02X%02X%02X%02X%02X%02X%02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); + + nfc->dev->format = NfcDeviceSaveFormatNfcV; + + if(nfc_save_file(nfc)) { + popup_set_header(popup, "Successfully\nsaved", 94, 3, AlignCenter, AlignTop); + } else { + popup_set_header( + popup, "Unlocked but\nsave failed!", 94, 3, AlignCenter, AlignTop); + } + } else { + popup_set_header(popup, "Successfully\nunlocked", 94, 3, AlignCenter, AlignTop); + } + + notification_message(nfc->notifications, &sequence_single_vibro); + //notification_message(nfc->notifications, &sequence_success); + + popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); + popup_set_context(popup, nfc); + popup_set_callback(popup, nfc_scene_nfcv_unlock_popup_callback); + popup_set_timeout(popup, 1500); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + + } else if(state == NfcSceneNfcVUnlockStateAlreadyUnlocked) { + popup_reset(popup); + + popup_set_header(popup, "Already\nUnlocked!", 94, 3, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); + popup_set_context(popup, nfc); + popup_set_callback(popup, nfc_scene_nfcv_unlock_popup_callback); + popup_set_timeout(popup, 1500); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + } else if(state == NfcSceneNfcVUnlockStateNotSupportedCard) { + popup_reset(popup); + popup_set_header(popup, "Wrong Type Of Card!", 64, 3, AlignCenter, AlignTop); + popup_set_text(popup, nfcv_data->error, 4, 22, AlignLeft, AlignTop); + popup_set_icon(popup, 73, 20, &I_DolphinCommon_56x48); + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlock, state); + } +} + +void nfc_scene_nfcv_unlock_on_enter(void* context) { + Nfc* nfc = context; + + nfc_device_clear(nfc->dev); + // Setup view + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateDetecting); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + + // Start worker + nfc_worker_start( + nfc->worker, + NfcWorkerStateNfcVUnlockAndSave, + &nfc->dev->dev_data, + nfc_scene_nfcv_unlock_worker_callback, + nfc); + + nfc_blink_read_start(nfc); +} + +bool nfc_scene_nfcv_unlock_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcWorkerEventCardDetected) { + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateUnlocked); + consumed = true; + } else if(event.event == NfcWorkerEventAborted) { + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateAlreadyUnlocked); + consumed = true; + } else if(event.event == NfcWorkerEventNoCardDetected) { + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateDetecting); + consumed = true; + } else if(event.event == NfcWorkerEventWrongCardDetected) { + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateNotSupportedCard); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneNfcVUnlockMenu); + } + return consumed; +} + +void nfc_scene_nfcv_unlock_on_exit(void* context) { + Nfc* nfc = context; + + // Stop worker + nfc_worker_stop(nfc->worker); + // Clear view + popup_reset(nfc->popup); + nfc_blink_stop(nfc); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVUnlock, NfcSceneNfcVUnlockStateIdle); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c new file mode 100644 index 000000000..9c4c81fbd --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c @@ -0,0 +1,60 @@ +#include "../nfc_i.h" +#include + +enum SubmenuIndex { + SubmenuIndexNfcVUnlockMenuManual, + SubmenuIndexNfcVUnlockMenuTonieBox, +}; + +void nfc_scene_nfcv_unlock_menu_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_nfcv_unlock_menu_on_enter(void* context) { + Nfc* nfc = context; + Submenu* submenu = nfc->submenu; + + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu); + submenu_add_item( + submenu, + "Enter PWD Manually", + SubmenuIndexNfcVUnlockMenuManual, + nfc_scene_nfcv_unlock_menu_submenu_callback, + nfc); + submenu_add_item( + submenu, + "Auth As TonieBox", + SubmenuIndexNfcVUnlockMenuTonieBox, + nfc_scene_nfcv_unlock_menu_submenu_callback, + nfc); + submenu_set_selected_item(submenu, state); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_nfcv_unlock_menu_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexNfcVUnlockMenuManual) { + nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodManual; + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVKeyInput); + consumed = true; + } else if(event.event == SubmenuIndexNfcVUnlockMenuTonieBox) { + nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodTonieBox; + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock); + DOLPHIN_DEED(DolphinDeedNfcRead); + consumed = true; + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu, event.event); + } + return consumed; +} + +void nfc_scene_nfcv_unlock_menu_on_exit(void* context) { + Nfc* nfc = context; + + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index 4252883b2..d30706c5b 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -68,6 +68,11 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcaReadSuccess); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; + } else if(event.event == NfcWorkerEventReadNfcV) { + notification_message(nfc->notifications, &sequence_success); + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + consumed = true; } else if(event.event == NfcWorkerEventReadMfUltralight) { notification_message(nfc->notifications, &sequence_success); // Set unlock password input to 0xFFFFFFFF only on fresh read diff --git a/applications/main/nfc/scenes/nfc_scene_rpc.c b/applications/main/nfc/scenes/nfc_scene_rpc.c index 60d01a30d..d06ee7564 100644 --- a/applications/main/nfc/scenes/nfc_scene_rpc.c +++ b/applications/main/nfc/scenes/nfc_scene_rpc.c @@ -55,6 +55,13 @@ bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) { &nfc->dev->dev_data, nfc_scene_rpc_emulate_callback, nfc); + } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { + nfc_worker_start( + nfc->worker, + NfcWorkerStateNfcVEmulate, + &nfc->dev->dev_data, + nfc_scene_rpc_emulate_callback, + nfc); } else { nfc_worker_start( nfc->worker, NfcWorkerStateUidEmulate, &nfc->dev->dev_data, NULL, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index e52493f2a..15771bcc3 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -44,6 +44,7 @@ void nfc_scene_saved_menu_on_enter(void* context) { } } else if( nfc->dev->format == NfcDeviceSaveFormatMifareUl || + nfc->dev->format == NfcDeviceSaveFormatNfcV || nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { submenu_add_item( submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); @@ -117,6 +118,8 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); + } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); } diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 9016f9274..84d0beda8 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -2139,6 +2139,14 @@ Function,-,nfca_get_crc16,uint16_t,"uint8_t*, uint16_t" Function,-,nfca_signal_alloc,NfcaSignal*, Function,-,nfca_signal_encode,void,"NfcaSignal*, uint8_t*, uint16_t, uint8_t*" Function,-,nfca_signal_free,void,NfcaSignal* +Function,+,nfcv_emu_deinit,void,NfcVData* +Function,+,nfcv_emu_init,void,"FuriHalNfcDevData*, NfcVData*" +Function,+,nfcv_emu_loop,_Bool,"FuriHalNfcTxRxContext*, FuriHalNfcDevData*, NfcVData*, uint32_t" +Function,+,nfcv_emu_send,void,"FuriHalNfcTxRxContext*, NfcVData*, uint8_t*, uint8_t, NfcVSendFlags, uint32_t" +Function,-,nfcv_inventory,ReturnCode,uint8_t* +Function,-,nfcv_read_blocks,ReturnCode,"NfcVReader*, NfcVData*" +Function,-,nfcv_read_card,_Bool,"NfcVReader*, FuriHalNfcDevData*, NfcVData*" +Function,-,nfcv_read_sysinfo,ReturnCode,"FuriHalNfcDevData*, NfcVData*" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 3c9cf408a..bdbc62e50 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -58,6 +58,8 @@ static void nfc_device_prepare_format_string(NfcDevice* dev, FuriString* format_ furi_string_set(format_string, "Mifare Classic"); } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { furi_string_set(format_string, "Mifare DESFire"); + } else if(dev->format == NfcDeviceSaveFormatNfcV) { + furi_string_set(format_string, "ISO15693"); } else { furi_string_set(format_string, "Unknown"); } @@ -93,6 +95,11 @@ static bool nfc_device_parse_format_string(NfcDevice* dev, FuriString* format_st dev->dev_data.protocol = NfcDeviceProtocolMifareDesfire; return true; } + if(furi_string_start_with_str(format_string, "ISO15693")) { + dev->format = NfcDeviceSaveFormatNfcV; + dev->dev_data.protocol = NfcDeviceProtocolNfcV; + return true; + } return false; } @@ -650,6 +657,298 @@ bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { return parsed; } +static bool nfc_device_save_slix_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + if(!flipper_format_write_comment_cstr(file, "SLIX specific data")) break; + if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_slix_s_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + if(!flipper_format_write_comment_cstr(file, "SLIX-S specific data")) break; + if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + if(!flipper_format_write_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + if(!flipper_format_write_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_write_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break; + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix_s_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + if(!flipper_format_read_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + if(!flipper_format_read_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_read_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_slix_l_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + if(!flipper_format_write_comment_cstr(file, "SLIX-L specific data")) break; + if(!flipper_format_write_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_write_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break; + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix_l_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + if(!flipper_format_read_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_read_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_slix2_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + if(!flipper_format_write_comment_cstr(file, "SLIX2 specific data")) break; + if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + if(!flipper_format_write_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + if(!flipper_format_write_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_write_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break; + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix2_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + if(!flipper_format_read_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + if(!flipper_format_read_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_read_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVData* data = &dev->dev_data.nfcv_data; + + do { + uint32_t temp_uint32 = 0; + uint8_t temp_uint8 = 0; + + if(!flipper_format_write_comment_cstr(file, "Data Storage Format Identifier")) break; + if(!flipper_format_write_hex(file, "DSFID", &(data->dsfid), 1)) break; + if(!flipper_format_write_comment_cstr(file, "Application Family Identifier")) break; + if(!flipper_format_write_hex(file, "AFI", &(data->afi), 1)) break; + if(!flipper_format_write_hex(file, "IC Reference", &(data->ic_ref), 1)) break; + temp_uint32 = data->block_num; + if(!flipper_format_write_comment_cstr(file, "Number of memory blocks, usually 0 to 256")) + break; + if(!flipper_format_write_uint32(file, "Block Count", &temp_uint32, 1)) break; + if(!flipper_format_write_comment_cstr(file, "Size of a single memory block, usually 4")) + break; + if(!flipper_format_write_hex(file, "Block Size", &(data->block_size), 1)) break; + if(!flipper_format_write_hex( + file, "Data Content", data->data, data->block_num * data->block_size)) + break; + if(!flipper_format_write_comment_cstr( + file, "First byte: DSFID (0x01) / AFI (0x02) lock info, others: block lock info")) + break; + if(!flipper_format_write_hex( + file, "Security Status", data->security_status, 1 + data->block_num)) + break; + if(!flipper_format_write_comment_cstr( + file, + "Subtype of this card (0 = ISO15693, 1 = SLIX, 2 = SLIX-S, 3 = SLIX-L, 4 = SLIX2)")) + break; + temp_uint8 = (uint8_t)data->sub_type; + if(!flipper_format_write_hex(file, "Subtype", &temp_uint8, 1)) break; + + switch(data->sub_type) { + case NfcVTypePlain: + if(!flipper_format_write_comment_cstr(file, "End of ISO15693 parameters")) break; + saved = true; + break; + case NfcVTypeSlix: + saved = nfc_device_save_slix_data(file, dev); + break; + case NfcVTypeSlixS: + saved = nfc_device_save_slix_s_data(file, dev); + break; + case NfcVTypeSlixL: + saved = nfc_device_save_slix_l_data(file, dev); + break; + case NfcVTypeSlix2: + saved = nfc_device_save_slix2_data(file, dev); + break; + default: + break; + } + } while(false); + + return saved; +} + +bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVData* data = &dev->dev_data.nfcv_data; + + memset(data, 0x00, sizeof(NfcVData)); + + do { + uint32_t temp_uint32 = 0; + uint8_t temp_value = 0; + + if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) break; + if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) break; + if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) break; + if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) break; + data->block_num = temp_uint32; + if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) break; + if(!flipper_format_read_hex( + file, "Data Content", data->data, data->block_num * data->block_size)) + break; + + /* optional, as added later */ + if(flipper_format_key_exist(file, "Security Status")) { + if(!flipper_format_read_hex( + file, "Security Status", data->security_status, 1 + data->block_num)) + break; + } + if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) break; + data->sub_type = temp_value; + + switch(data->sub_type) { + case NfcVTypePlain: + parsed = true; + break; + case NfcVTypeSlix: + parsed = nfc_device_load_slix_data(file, dev); + break; + case NfcVTypeSlixS: + parsed = nfc_device_load_slix_s_data(file, dev); + break; + case NfcVTypeSlixL: + parsed = nfc_device_load_slix_l_data(file, dev); + break; + case NfcVTypeSlix2: + parsed = nfc_device_load_slix2_data(file, dev); + break; + default: + break; + } + } while(false); + + return parsed; +} + static bool nfc_device_save_bank_card_data(FlipperFormat* file, NfcDevice* dev) { bool saved = false; EmvData* data = &dev->dev_data.emv_data; @@ -692,11 +991,17 @@ bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) { if(!flipper_format_get_value_count(file, "AID", &data_cnt)) break; data->aid_len = data_cnt; if(!flipper_format_read_hex(file, "AID", data->aid, data->aid_len)) break; - if(!flipper_format_read_string(file, "Name", temp_str)) break; + if(!flipper_format_read_string(file, "Name", temp_str)) { + furi_string_set_str(temp_str, "Unknown"); + } strlcpy(data->name, furi_string_get_cstr(temp_str), sizeof(data->name)); - if(!flipper_format_get_value_count(file, "Number", &data_cnt)) break; + if(!flipper_format_get_value_count(file, "Number", &data_cnt)) { + data_cnt = 0; + } data->number_len = data_cnt; - if(!flipper_format_read_hex(file, "Number", data->number, data->number_len)) break; + if(!flipper_format_read_hex(file, "Number", data->number, data->number_len)) { + memset(data->number, 0, sizeof(data->number)); + }; parsed = true; // Load optional data uint8_t exp_data[2] = {}; @@ -1097,23 +1402,30 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name) { if(!flipper_format_write_header_cstr(file, nfc_file_header, nfc_file_version)) break; // Write nfc device type if(!flipper_format_write_comment_cstr( - file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card")) + file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic or ISO15693")) break; nfc_device_prepare_format_string(dev, temp_str); if(!flipper_format_write_string(file, "Device type", temp_str)) break; - // Write UID, ATQA, SAK - if(!flipper_format_write_comment_cstr(file, "UID, ATQA and SAK are common for all formats")) - break; + // Write UID + if(!flipper_format_write_comment_cstr(file, "UID is common for all formats")) break; if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break; - // Save ATQA in MSB order for correct companion apps display - uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; - if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break; - if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break; + + if(dev->format != NfcDeviceSaveFormatNfcV) { + // Write ATQA, SAK + if(!flipper_format_write_comment_cstr(file, "ISO14443 specific fields")) break; + // Save ATQA in MSB order for correct companion apps display + uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; + if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break; + if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break; + } + // Save more data if necessary if(dev->format == NfcDeviceSaveFormatMifareUl) { if(!nfc_device_save_mifare_ul_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { if(!nfc_device_save_mifare_df_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatNfcV) { + if(!nfc_device_save_nfcv_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatBankCard) { if(!nfc_device_save_bank_card_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { @@ -1190,18 +1502,20 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia if(!nfc_device_parse_format_string(dev, temp_str)) break; // Read and parse UID, ATQA and SAK if(!flipper_format_get_value_count(file, "UID", &data_cnt)) break; - if(!(data_cnt == 4 || data_cnt == 7)) break; + if(!(data_cnt == 4 || data_cnt == 7 || data_cnt == 8)) break; data->uid_len = data_cnt; if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; - if(version == version_with_lsb_atqa) { - if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; - } else { - uint8_t atqa[2] = {}; - if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break; - data->atqa[0] = atqa[1]; - data->atqa[1] = atqa[0]; + if(dev->format != NfcDeviceSaveFormatNfcV) { + if(version == version_with_lsb_atqa) { + if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; + } else { + uint8_t atqa[2] = {}; + if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break; + data->atqa[0] = atqa[1]; + data->atqa[1] = atqa[0]; + } + if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; } - if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; // Load CUID uint8_t* cuid_start = data->uid; if(data->uid_len == 7) { @@ -1216,6 +1530,8 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia if(!nfc_device_load_mifare_classic_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { if(!nfc_device_load_mifare_df_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatNfcV) { + if(!nfc_device_load_nfcv_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatBankCard) { if(!nfc_device_load_bank_card_data(file, dev)) break; } diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 8b2e6e5ba..d5a9e57fb 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -31,6 +32,7 @@ typedef enum { NfcDeviceProtocolMifareUl, NfcDeviceProtocolMifareClassic, NfcDeviceProtocolMifareDesfire, + NfcDeviceProtocolNfcV } NfcProtocol; typedef enum { @@ -39,6 +41,7 @@ typedef enum { NfcDeviceSaveFormatMifareUl, NfcDeviceSaveFormatMifareClassic, NfcDeviceSaveFormatMifareDesfire, + NfcDeviceSaveFormatNfcV, } NfcDeviceSaveFormat; typedef struct { @@ -74,6 +77,7 @@ typedef struct { MfUltralightData mf_ul_data; MfClassicData mf_classic_data; MifareDesfireData mf_df_data; + NfcVData nfcv_data; }; FuriString* parsed_data; } NfcDeviceData; diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index ae6c504b0..197b6f58a 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -111,6 +111,14 @@ int32_t nfc_worker_task(void* context) { nfc_worker_mf_classic_dict_attack(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateAnalyzeReader) { nfc_worker_analyze_reader(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVEmulate) { + nfc_worker_nfcv_emulate(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVSniff) { + nfc_worker_nfcv_sniff(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVUnlock) { + nfc_worker_nfcv_unlock(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) { + nfc_worker_nfcv_unlock(nfc_worker); } furi_hal_nfc_sleep(); nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); @@ -118,6 +126,239 @@ int32_t nfc_worker_task(void* context) { return 0; } +static bool nfc_worker_read_nfcv(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + bool read_success = false; + NfcVReader reader = {}; + + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + + furi_hal_nfc_sleep(); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + do { + if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break; + if(!nfcv_read_card(&reader, nfc_data, nfcv_data)) break; + + read_success = true; + } while(false); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } + + return read_success; +} + +void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + nfcv_emu_init(nfc_data, nfcv_data); + while(nfc_worker->state == NfcWorkerStateNfcVEmulate) { + if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 100)) { + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventNfcVCommandExecuted, nfc_worker->context); + if(nfcv_data->modified) { + nfc_worker->callback(NfcWorkerEventNfcVContentChanged, nfc_worker->context); + nfcv_data->modified = false; + } + } + } + furi_delay_ms(10); + } + nfcv_emu_deinit(nfcv_data); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + +void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + nfcv_data->sub_type = NfcVTypeSniff; + nfcv_emu_init(nfc_data, nfcv_data); + + while(nfc_worker->state == NfcWorkerStateNfcVSniff) { + if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 100)) { + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventNfcVCommandExecuted, nfc_worker->context); + } + } + furi_delay_ms(10); + } + nfcv_emu_deinit(nfcv_data); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + +void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + FuriHalNfcTxRxContext tx_rx = {}; + uint8_t* key_data = nfcv_data->sub_data.slix.key_privacy; + uint32_t key = 0; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + furi_hal_nfc_sleep(); + + while((nfc_worker->state == NfcWorkerStateNfcVUnlock) || + (nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave)) { + furi_hal_nfc_exit_sleep(); + furi_hal_nfc_ll_txrx_on(); + furi_hal_nfc_ll_poll(); + if(furi_hal_nfc_ll_set_mode( + FuriHalNfcModePollNfcv, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48) != + FuriHalNfcReturnOk) { + break; + } + + furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCV_POLLER); + furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCV_POLLER); + furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); + furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCV); + + furi_hal_console_printf("Detect presence\r\n"); + ReturnCode ret = slix_get_random(nfcv_data); + + if(ret == ERR_NONE) { + /* there is some chip, responding with a RAND */ + nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV; + furi_hal_console_printf(" Chip detected. In privacy?\r\n"); + ret = nfcv_inventory(NULL); + + if(ret == ERR_NONE) { + /* chip is also visible, so no action required, just save */ + if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) { + NfcVReader reader = {}; + + if(!nfcv_read_card(&reader, &nfc_worker->dev_data->nfc_data, nfcv_data)) { + furi_hal_console_printf(" => failed, wait for chip to disappear.\r\n"); + snprintf(nfcv_data->error, sizeof(nfcv_data->error), "Read card\nfailed"); + nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); + } else { + furi_hal_console_printf(" => success, wait for chip to disappear.\r\n"); + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + } + } else { + furi_hal_console_printf(" => success, wait for chip to disappear.\r\n"); + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + } + + while(slix_get_random(NULL) == ERR_NONE) { + furi_delay_ms(100); + } + + furi_hal_console_printf( + " => chip is already visible, wait for chip to disappear.\r\n"); + nfc_worker->callback(NfcWorkerEventAborted, nfc_worker->context); + while(slix_get_random(NULL) == ERR_NONE) { + furi_delay_ms(100); + } + + key_data[0] = 0; + key_data[1] = 0; + key_data[2] = 0; + key_data[3] = 0; + + } else { + /* chip is invisible, try to unlock */ + furi_hal_console_printf(" chip is invisible, unlocking\r\n"); + + if(nfcv_data->auth_method == NfcVAuthMethodManual) { + key |= key_data[0] << 24; + key |= key_data[1] << 16; + key |= key_data[2] << 8; + key |= key_data[3] << 0; + + ret = slix_unlock(nfcv_data, 4); + } else { + key = 0x7FFD6E5B; + key_data[0] = key >> 24; + key_data[1] = key >> 16; + key_data[2] = key >> 8; + key_data[3] = key >> 0; + ret = slix_unlock(nfcv_data, 4); + + if(ret != ERR_NONE) { + /* main key failed, trying second one */ + furi_hal_console_printf(" trying second key after resetting\r\n"); + + /* reset chip */ + furi_hal_nfc_ll_txrx_off(); + furi_delay_ms(20); + furi_hal_nfc_ll_txrx_on(); + + if(slix_get_random(nfcv_data) != ERR_NONE) { + furi_hal_console_printf(" reset failed\r\n"); + } + + key = 0x0F0F0F0F; + key_data[0] = key >> 24; + key_data[1] = key >> 16; + key_data[2] = key >> 8; + key_data[3] = key >> 0; + ret = slix_unlock(nfcv_data, 4); + } + } + if(ret != ERR_NONE) { + /* unlock failed */ + furi_hal_console_printf(" => failed, wait for chip to disappear.\r\n"); + snprintf( + nfcv_data->error, sizeof(nfcv_data->error), "Passwords not\naccepted"); + nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); + + /* reset chip */ + furi_hal_nfc_ll_txrx_off(); + furi_delay_ms(20); + furi_hal_nfc_ll_txrx_on(); + + /* wait for disappearing */ + while(slix_get_random(NULL) == ERR_NONE) { + furi_delay_ms(100); + } + } + } + } else { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + } + + furi_hal_nfc_ll_txrx_off(); + furi_hal_nfc_sleep(); + furi_delay_ms(100); + } + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { bool read_success = false; MfUltralightReader reader = {}; @@ -378,7 +619,12 @@ void nfc_worker_read(NfcWorker* nfc_worker) { event = NfcWorkerEventReadUidNfcF; break; } else if(nfc_data->type == FuriHalNfcTypeV) { - event = NfcWorkerEventReadUidNfcV; + FURI_LOG_I(TAG, "NfcV detected"); + nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV; + if(nfc_worker_read_nfcv(nfc_worker, &tx_rx)) { + FURI_LOG_I(TAG, "nfc_worker_read_nfcv success"); + } + event = NfcWorkerEventReadNfcV; break; } } else { diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index ce542828a..8918d8e8b 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -18,6 +18,10 @@ typedef enum { NfcWorkerStateReadMfUltralightReadAuth, NfcWorkerStateMfClassicDictAttack, NfcWorkerStateAnalyzeReader, + NfcWorkerStateNfcVEmulate, + NfcWorkerStateNfcVUnlock, + NfcWorkerStateNfcVUnlockAndSave, + NfcWorkerStateNfcVSniff, // Debug NfcWorkerStateEmulateApdu, NfcWorkerStateField, @@ -40,6 +44,7 @@ typedef enum { NfcWorkerEventReadMfClassicLoadKeyCache, NfcWorkerEventReadMfClassicDictAttackRequired, NfcWorkerEventReadBankCard, + NfcWorkerEventReadNfcV, // Nfc worker common events NfcWorkerEventSuccess, @@ -70,6 +75,9 @@ typedef enum { // Mifare Ultralight events NfcWorkerEventMfUltralightPassKey, // NFC worker requesting manual key NfcWorkerEventMfUltralightPwdAuth, // Reader sent auth command + NfcWorkerEventNfcVPassKey, // NFC worker requesting manual key + NfcWorkerEventNfcVCommandExecuted, + NfcWorkerEventNfcVContentChanged, } NfcWorkerEvent; typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context); @@ -88,3 +96,6 @@ void nfc_worker_start( void* context); void nfc_worker_stop(NfcWorker* nfc_worker); +void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker); +void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker); +void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker); \ No newline at end of file diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h index 9733426ab..d13d3c5c8 100644 --- a/lib/nfc/nfc_worker_i.h +++ b/lib/nfc/nfc_worker_i.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include struct NfcWorker { diff --git a/lib/nfc/protocols/nfcv.c b/lib/nfc/protocols/nfcv.c new file mode 100644 index 000000000..471993b63 --- /dev/null +++ b/lib/nfc/protocols/nfcv.c @@ -0,0 +1,1393 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nfcv.h" +#include "nfc_util.h" +#include "slix.h" + +#define TAG "NfcV" + +/* macros to map "modulate field" flag to GPIO level */ +#define GPIO_LEVEL_MODULATED NFCV_LOAD_MODULATION_POLARITY +#define GPIO_LEVEL_UNMODULATED (!GPIO_LEVEL_MODULATED) + +/* timing macros */ +#define DIGITAL_SIGNAL_UNIT_S (100000000000.0f) +#define DIGITAL_SIGNAL_UNIT_US (100000.0f) + +ReturnCode nfcv_inventory(uint8_t* uid) { + uint16_t received = 0; + rfalNfcvInventoryRes res; + ReturnCode ret = ERR_NONE; + + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + /* TODO: needs proper abstraction via fury_hal(_ll)_* */ + ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, &res, &received); + + if(ret == ERR_NONE) { + break; + } + } + + if(ret == ERR_NONE) { + if(uid != NULL) { + memcpy(uid, res.UID, NFCV_UID_LENGTH); + } + } + + return ret; +} + +ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* nfcv_data) { + UNUSED(reader); + + uint16_t received = 0; + for(size_t block = 0; block < nfcv_data->block_num; block++) { + uint8_t rxBuf[32]; + FURI_LOG_D(TAG, "Reading block %d/%d", block, (nfcv_data->block_num - 1)); + + ReturnCode ret = ERR_NONE; + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + ret = rfalNfcvPollerReadSingleBlock( + RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, block, rxBuf, sizeof(rxBuf), &received); + + if(ret == ERR_NONE) { + break; + } + } + if(ret != ERR_NONE) { + FURI_LOG_D(TAG, "failed to read: %d", ret); + return ret; + } + memcpy( + &(nfcv_data->data[block * nfcv_data->block_size]), &rxBuf[1], nfcv_data->block_size); + FURI_LOG_D( + TAG, + " %02X %02X %02X %02X", + nfcv_data->data[block * nfcv_data->block_size + 0], + nfcv_data->data[block * nfcv_data->block_size + 1], + nfcv_data->data[block * nfcv_data->block_size + 2], + nfcv_data->data[block * nfcv_data->block_size + 3]); + } + + return ERR_NONE; +} + +ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + uint8_t rxBuf[32]; + uint16_t received = 0; + ReturnCode ret = ERR_NONE; + + FURI_LOG_D(TAG, "Read SYSTEM INFORMATION..."); + + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + /* TODO: needs proper abstraction via fury_hal(_ll)_* */ + ret = rfalNfcvPollerGetSystemInformation( + RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, rxBuf, sizeof(rxBuf), &received); + + if(ret == ERR_NONE) { + break; + } + } + + if(ret == ERR_NONE) { + nfc_data->type = FuriHalNfcTypeV; + nfc_data->uid_len = NFCV_UID_LENGTH; + /* UID is stored reversed in this response */ + for(int pos = 0; pos < nfc_data->uid_len; pos++) { + nfc_data->uid[pos] = rxBuf[2 + (NFCV_UID_LENGTH - 1 - pos)]; + } + nfcv_data->dsfid = rxBuf[NFCV_UID_LENGTH + 2]; + nfcv_data->afi = rxBuf[NFCV_UID_LENGTH + 3]; + nfcv_data->block_num = rxBuf[NFCV_UID_LENGTH + 4] + 1; + nfcv_data->block_size = rxBuf[NFCV_UID_LENGTH + 5] + 1; + nfcv_data->ic_ref = rxBuf[NFCV_UID_LENGTH + 6]; + FURI_LOG_D( + TAG, + " UID: %02X %02X %02X %02X %02X %02X %02X %02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); + FURI_LOG_D( + TAG, + " DSFID %d, AFI %d, Blocks %d, Size %d, IC Ref %d", + nfcv_data->dsfid, + nfcv_data->afi, + nfcv_data->block_num, + nfcv_data->block_size, + nfcv_data->ic_ref); + return ret; + } + FURI_LOG_D(TAG, "Failed: %d", ret); + + return ret; +} + +bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + furi_assert(reader); + furi_assert(nfc_data); + furi_assert(nfcv_data); + + if(nfcv_read_sysinfo(nfc_data, nfcv_data) != ERR_NONE) { + return false; + } + + if(nfcv_read_blocks(reader, nfcv_data) != ERR_NONE) { + return false; + } + + if(slix_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX detected"); + nfcv_data->sub_type = NfcVTypeSlix; + } else if(slix2_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX2 detected"); + nfcv_data->sub_type = NfcVTypeSlix2; + } else if(slix_s_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX-S detected"); + nfcv_data->sub_type = NfcVTypeSlixS; + } else if(slix_l_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX-L detected"); + nfcv_data->sub_type = NfcVTypeSlixL; + } else { + nfcv_data->sub_type = NfcVTypePlain; + } + + return true; +} + +void nfcv_crc(uint8_t* data, uint32_t length) { + uint32_t reg = 0xFFFF; + + for(size_t i = 0; i < length; i++) { + reg = reg ^ ((uint32_t)data[i]); + for(size_t j = 0; j < 8; j++) { + if(reg & 0x0001) { + reg = (reg >> 1) ^ 0x8408; + } else { + reg = (reg >> 1); + } + } + } + + uint16_t crc = ~(uint16_t)(reg & 0xffff); + + data[length + 0] = crc & 0xFF; + data[length + 1] = crc >> 8; +} + +void nfcv_emu_free_signals(NfcVEmuAirSignals* signals) { + furi_assert(signals); + + if(signals->nfcv_resp_one) { + digital_signal_free(signals->nfcv_resp_one); + } + if(signals->nfcv_resp_zero) { + digital_signal_free(signals->nfcv_resp_zero); + } + if(signals->nfcv_resp_sof) { + digital_signal_free(signals->nfcv_resp_sof); + } + if(signals->nfcv_resp_eof) { + digital_signal_free(signals->nfcv_resp_eof); + } + signals->nfcv_resp_one = NULL; + signals->nfcv_resp_zero = NULL; + signals->nfcv_resp_sof = NULL; + signals->nfcv_resp_eof = NULL; +} + +bool nfcv_emu_alloc_signals(NfcVEmuAir* air, NfcVEmuAirSignals* signals, uint32_t slowdown) { + furi_assert(air); + furi_assert(signals); + + bool success = true; + + if(!signals->nfcv_resp_one) { + /* logical one: unmodulated then 8 pulses */ + signals->nfcv_resp_one = digital_signal_alloc( + slowdown * (air->nfcv_resp_unmod->edge_cnt + 8 * air->nfcv_resp_pulse->edge_cnt)); + if(!signals->nfcv_resp_one) { + return false; + } + for(size_t i = 0; i < slowdown; i++) { + success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_unmod); + } + for(size_t i = 0; i < slowdown * 8; i++) { + success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_pulse); + } + if(!success) { + return false; + } + } + if(!signals->nfcv_resp_zero) { + /* logical zero: 8 pulses then unmodulated */ + signals->nfcv_resp_zero = digital_signal_alloc( + slowdown * (8 * air->nfcv_resp_pulse->edge_cnt + air->nfcv_resp_unmod->edge_cnt)); + if(!signals->nfcv_resp_zero) { + return false; + } + for(size_t i = 0; i < slowdown * 8; i++) { + success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_pulse); + } + for(size_t i = 0; i < slowdown; i++) { + success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_unmod); + } + if(!success) { + return false; + } + } + if(!signals->nfcv_resp_sof) { + /* SOF: unmodulated, 24 pulses, logic 1 */ + signals->nfcv_resp_sof = digital_signal_alloc( + slowdown * (3 * air->nfcv_resp_unmod->edge_cnt + 24 * air->nfcv_resp_pulse->edge_cnt) + + signals->nfcv_resp_one->edge_cnt); + if(!signals->nfcv_resp_sof) { + return false; + } + for(size_t i = 0; i < slowdown * 3; i++) { + success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_unmod); + } + for(size_t i = 0; i < slowdown * 24; i++) { + success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_pulse); + } + success &= digital_signal_append(signals->nfcv_resp_sof, signals->nfcv_resp_one); + if(!success) { + return false; + } + } + if(!signals->nfcv_resp_eof) { + /* EOF: logic 0, 24 pulses, unmodulated */ + signals->nfcv_resp_eof = digital_signal_alloc( + signals->nfcv_resp_zero->edge_cnt + + slowdown * (24 * air->nfcv_resp_pulse->edge_cnt + 3 * air->nfcv_resp_unmod->edge_cnt) + + air->nfcv_resp_unmod->edge_cnt); + if(!signals->nfcv_resp_eof) { + return false; + } + success &= digital_signal_append(signals->nfcv_resp_eof, signals->nfcv_resp_zero); + for(size_t i = 0; i < slowdown * 23; i++) { + success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_pulse); + } + /* we don't want to add the last level as we just want a transition to "unmodulated" again */ + for(size_t i = 0; i < slowdown; i++) { + success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_half_pulse); + } + } + return success; +} + +bool nfcv_emu_alloc(NfcVData* nfcv_data) { + furi_assert(nfcv_data); + + if(!nfcv_data->frame) { + nfcv_data->frame = malloc(NFCV_FRAMESIZE_MAX); + if(!nfcv_data->frame) { + return false; + } + } + + if(!nfcv_data->emu_air.nfcv_signal) { + /* assuming max frame length is 255 bytes */ + nfcv_data->emu_air.nfcv_signal = digital_sequence_alloc(8 * 255 + 2, &gpio_spi_r_mosi); + if(!nfcv_data->emu_air.nfcv_signal) { + return false; + } + } + if(!nfcv_data->emu_air.nfcv_resp_unmod) { + /* unmodulated 256/fc or 1024/fc signal as building block */ + nfcv_data->emu_air.nfcv_resp_unmod = digital_signal_alloc(4); + if(!nfcv_data->emu_air.nfcv_resp_unmod) { + return false; + } + nfcv_data->emu_air.nfcv_resp_unmod->start_level = GPIO_LEVEL_UNMODULATED; + nfcv_data->emu_air.nfcv_resp_unmod->edge_timings[0] = + (uint32_t)(NFCV_RESP_SUBC1_UNMOD_256 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_unmod->edge_cnt = 1; + } + if(!nfcv_data->emu_air.nfcv_resp_pulse) { + /* modulated fc/32 or fc/8 pulse as building block */ + nfcv_data->emu_air.nfcv_resp_pulse = digital_signal_alloc(4); + if(!nfcv_data->emu_air.nfcv_resp_pulse) { + return false; + } + nfcv_data->emu_air.nfcv_resp_pulse->start_level = GPIO_LEVEL_MODULATED; + nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[0] = + (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[1] = + (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_pulse->edge_cnt = 2; + } + + if(!nfcv_data->emu_air.nfcv_resp_half_pulse) { + /* modulated fc/32 or fc/8 pulse as building block */ + nfcv_data->emu_air.nfcv_resp_half_pulse = digital_signal_alloc(4); + if(!nfcv_data->emu_air.nfcv_resp_half_pulse) { + return false; + } + nfcv_data->emu_air.nfcv_resp_half_pulse->start_level = GPIO_LEVEL_MODULATED; + nfcv_data->emu_air.nfcv_resp_half_pulse->edge_timings[0] = + (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_half_pulse->edge_cnt = 1; + } + + bool success = true; + success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_high, 1); + success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_low, 4); + + if(!success) { + FURI_LOG_E(TAG, "Failed to allocate signals"); + return false; + } + + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_SOF, + nfcv_data->emu_air.signals_high.nfcv_resp_sof); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_BIT0, + nfcv_data->emu_air.signals_high.nfcv_resp_zero); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_BIT1, + nfcv_data->emu_air.signals_high.nfcv_resp_one); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_EOF, + nfcv_data->emu_air.signals_high.nfcv_resp_eof); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_SOF, + nfcv_data->emu_air.signals_low.nfcv_resp_sof); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_BIT0, + nfcv_data->emu_air.signals_low.nfcv_resp_zero); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_BIT1, + nfcv_data->emu_air.signals_low.nfcv_resp_one); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_EOF, + nfcv_data->emu_air.signals_low.nfcv_resp_eof); + + return true; +} + +void nfcv_emu_free(NfcVData* nfcv_data) { + furi_assert(nfcv_data); + + if(nfcv_data->frame) { + free(nfcv_data->frame); + } + if(nfcv_data->emu_protocol_ctx) { + free(nfcv_data->emu_protocol_ctx); + } + if(nfcv_data->emu_air.nfcv_resp_unmod) { + digital_signal_free(nfcv_data->emu_air.nfcv_resp_unmod); + } + if(nfcv_data->emu_air.nfcv_resp_pulse) { + digital_signal_free(nfcv_data->emu_air.nfcv_resp_pulse); + } + if(nfcv_data->emu_air.nfcv_resp_half_pulse) { + digital_signal_free(nfcv_data->emu_air.nfcv_resp_half_pulse); + } + if(nfcv_data->emu_air.nfcv_signal) { + digital_sequence_free(nfcv_data->emu_air.nfcv_signal); + } + if(nfcv_data->emu_air.reader_signal) { + pulse_reader_free(nfcv_data->emu_air.reader_signal); + } + + nfcv_data->frame = NULL; + nfcv_data->emu_air.nfcv_resp_unmod = NULL; + nfcv_data->emu_air.nfcv_resp_pulse = NULL; + nfcv_data->emu_air.nfcv_resp_half_pulse = NULL; + nfcv_data->emu_air.nfcv_signal = NULL; + nfcv_data->emu_air.reader_signal = NULL; + + nfcv_emu_free_signals(&nfcv_data->emu_air.signals_high); + nfcv_emu_free_signals(&nfcv_data->emu_air.signals_low); +} + +void nfcv_emu_send( + FuriHalNfcTxRxContext* tx_rx, + NfcVData* nfcv, + uint8_t* data, + uint8_t length, + NfcVSendFlags flags, + uint32_t send_time) { + furi_assert(tx_rx); + furi_assert(nfcv); + + /* picked default value (0) to match the most common format */ + if(!flags) { + flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof | + NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate; + } + + if(flags & NfcVSendFlagsCrc) { + nfcv_crc(data, length); + length += 2; + } + + /* depending on the request flags, send with high or low rate */ + uint32_t bit0 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT0 : NFCV_SIG_LOW_BIT0; + uint32_t bit1 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT1 : NFCV_SIG_LOW_BIT1; + uint32_t sof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_SOF : NFCV_SIG_LOW_SOF; + uint32_t eof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_EOF : NFCV_SIG_LOW_EOF; + + digital_sequence_clear(nfcv->emu_air.nfcv_signal); + + if(flags & NfcVSendFlagsSof) { + digital_sequence_add(nfcv->emu_air.nfcv_signal, sof); + } + + for(int bit_total = 0; bit_total < length * 8; bit_total++) { + uint32_t byte_pos = bit_total / 8; + uint32_t bit_pos = bit_total % 8; + uint8_t bit_val = 0x01 << bit_pos; + + digital_sequence_add(nfcv->emu_air.nfcv_signal, (data[byte_pos] & bit_val) ? bit1 : bit0); + } + + if(flags & NfcVSendFlagsEof) { + digital_sequence_add(nfcv->emu_air.nfcv_signal, eof); + } + + furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); + digital_sequence_set_sendtime(nfcv->emu_air.nfcv_signal, send_time); + digital_sequence_send(nfcv->emu_air.nfcv_signal); + furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); + + if(tx_rx->sniff_tx) { + tx_rx->sniff_tx(data, length * 8, false, tx_rx->sniff_context); + } +} + +static void nfcv_revuidcpy(uint8_t* dst, uint8_t* src) { + for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) { + dst[pos] = src[NFCV_UID_LENGTH - 1 - pos]; + } +} + +static int nfcv_revuidcmp(uint8_t* dst, uint8_t* src) { + for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) { + if(dst[pos] != src[NFCV_UID_LENGTH - 1 - pos]) { + return 1; + } + } + return 0; +} + +void nfcv_emu_handle_packet( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + + if(nfcv_data->frame_length < 2) { + return; + } + + if(nfcv_data->echo_mode) { + nfcv_emu_send( + tx_rx, + nfcv_data, + nfcv_data->frame, + nfcv_data->frame_length, + NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data"); + return; + } + + /* parse the frame data for the upcoming part 3 handling */ + ctx->flags = nfcv_data->frame[0]; + ctx->command = nfcv_data->frame[1]; + ctx->selected = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && (ctx->flags & NFCV_REQ_FLAG_SELECT); + ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && + (ctx->flags & NFCV_REQ_FLAG_ADDRESS); + ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED); + ctx->address_offset = 2 + (ctx->advanced ? 1 : 0); + ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0); + ctx->response_flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof; + ctx->send_time = nfcv_data->eof_timestamp + NFCV_FDT_FC(4380); + + if(ctx->flags & NFCV_REQ_FLAG_DATA_RATE) { + ctx->response_flags |= NfcVSendFlagsHighRate; + } + if(ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) { + ctx->response_flags |= NfcVSendFlagsTwoSubcarrier; + } + + if(ctx->payload_offset + 2 > nfcv_data->frame_length) { +#ifdef NFCV_VERBOSE + FURI_LOG_D(TAG, "command 0x%02X, but packet is too short", ctx->command); +#endif + return; + } + + /* standard behavior is implemented */ + if(ctx->addressed) { + uint8_t* address = &nfcv_data->frame[ctx->address_offset]; + if(nfcv_revuidcmp(address, nfc_data->uid)) { +#ifdef NFCV_VERBOSE + FURI_LOG_D(TAG, "addressed command 0x%02X, but not for us:", ctx->command); + FURI_LOG_D( + TAG, + " dest: %02X%02X%02X%02X%02X%02X%02X%02X", + address[7], + address[6], + address[5], + address[4], + address[3], + address[2], + address[1], + address[0]); + FURI_LOG_D( + TAG, + " our UID: %02X%02X%02X%02X%02X%02X%02X%02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); +#endif + return; + } + } + + if(ctx->selected && !nfcv_data->selected) { +#ifdef NFCV_VERBOSE + FURI_LOG_D( + TAG, + "selected card shall execute command 0x%02X, but we were not selected", + ctx->command); +#endif + return; + } + + /* then give control to the card subtype specific protocol filter */ + if(ctx->emu_protocol_filter != NULL) { + if(ctx->emu_protocol_filter(tx_rx, nfc_data, nfcv_data)) { + if(strlen(nfcv_data->last_command) > 0) { +#ifdef NFCV_VERBOSE + FURI_LOG_D( + TAG, "Received command %s (handled by filter)", nfcv_data->last_command); +#endif + } + return; + } + } + + switch(ctx->command) { + case NFCV_CMD_INVENTORY: { + bool respond = false; + + if(ctx->flags & NFCV_REQ_FLAG_AFI) { + uint8_t afi = nfcv_data->frame[ctx->payload_offset]; + if(afi == nfcv_data->afi) { + respond = true; + } + } else { + respond = true; + } + + if(!nfcv_data->quiet && respond) { + int buffer_pos = 0; + ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; + ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; + nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid); + buffer_pos += NFCV_UID_LENGTH; + + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + buffer_pos, + ctx->response_flags, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY"); + } else { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY (quiet)"); + } + break; + } + + case NFCV_CMD_STAY_QUIET: { + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "STAYQUIET"); + nfcv_data->quiet = true; + break; + } + + case NFCV_CMD_LOCK_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + nfcv_data->security_status[block] |= 0x01; + nfcv_data->modified = true; + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK BLOCK %d", block); + break; + } + + case NFCV_CMD_WRITE_DSFID: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + + if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) { + nfcv_data->dsfid = id; + nfcv_data->modified = true; + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE DSFID %02X", id); + break; + } + + case NFCV_CMD_WRITE_AFI: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + + if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) { + nfcv_data->afi = id; + nfcv_data->modified = true; + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE AFI %02X", id); + break; + } + + case NFCV_CMD_LOCK_DSFID: { + if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) { + nfcv_data->security_status[0] |= NfcVLockBitDsfid; + nfcv_data->modified = true; + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK DSFID"); + break; + } + + case NFCV_CMD_LOCK_AFI: { + if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) { + nfcv_data->security_status[0] |= NfcVLockBitAfi; + nfcv_data->modified = true; + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK AFI"); + break; + } + + case NFCV_CMD_SELECT: { + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_data->selected = true; + nfcv_data->quiet = false; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SELECT"); + break; + } + + case NFCV_CMD_RESET_TO_READY: { + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_data->quiet = false; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "RESET_TO_READY"); + break; + } + + case NFCV_CMD_READ_MULTI_BLOCK: + case NFCV_CMD_READ_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t blocks = 1; + + if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { + blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; + } + + if(block + blocks <= nfcv_data->block_num) { + uint8_t buffer_pos = 0; + + ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; + + for(int block_index = 0; block_index < blocks; block_index++) { + int block_current = block + block_index; + /* prepend security status */ + if(ctx->flags & NFCV_REQ_FLAG_OPTION) { + ctx->response_buffer[buffer_pos++] = + nfcv_data->security_status[1 + block_current]; + } + /* then the data block */ + memcpy( + &ctx->response_buffer[buffer_pos], + &nfcv_data->data[nfcv_data->block_size * block_current], + nfcv_data->block_size); + buffer_pos += nfcv_data->block_size; + } + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + buffer_pos, + ctx->response_flags, + ctx->send_time); + } else { + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); + } + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block); + + break; + } + + case NFCV_CMD_WRITE_MULTI_BLOCK: + case NFCV_CMD_WRITE_BLOCK: { + uint8_t blocks = 1; + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t data_pos = ctx->payload_offset + 1; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + blocks = nfcv_data->frame[data_pos] + 1; + data_pos++; + } + + uint8_t* data = &nfcv_data->frame[data_pos]; + uint32_t data_len = nfcv_data->block_size * blocks; + + if((block + blocks) <= nfcv_data->block_num && + (data_pos + data_len + 2) == nfcv_data->frame_length) { + ctx->response_buffer[0] = NFCV_NOERROR; + memcpy( + &nfcv_data->data[nfcv_data->block_size * block], + &nfcv_data->frame[data_pos], + data_len); + nfcv_data->modified = true; + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } else { + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); + } + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "WRITE MULTI BLOCK %d, %d blocks", + block, + blocks); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "WRITE BLOCK %d <- %02X %02X %02X %02X", + block, + data[0], + data[1], + data[2], + data[3]); + } + break; + } + + case NFCV_CMD_GET_SYSTEM_INFO: { + int buffer_pos = 0; + ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; + ctx->response_buffer[buffer_pos++] = NFCV_SYSINFO_FLAG_DSFID | NFCV_SYSINFO_FLAG_AFI | + NFCV_SYSINFO_FLAG_MEMSIZE | NFCV_SYSINFO_FLAG_ICREF; + nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid); + buffer_pos += NFCV_UID_LENGTH; + ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; /* DSFID */ + ctx->response_buffer[buffer_pos++] = nfcv_data->afi; /* AFI */ + ctx->response_buffer[buffer_pos++] = nfcv_data->block_num - 1; /* number of blocks */ + ctx->response_buffer[buffer_pos++] = nfcv_data->block_size - 1; /* block size */ + ctx->response_buffer[buffer_pos++] = nfcv_data->ic_ref; /* IC reference */ + + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + buffer_pos, + ctx->response_flags, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SYSTEMINFO"); + break; + } + + case NFCV_CMD_CUST_ECHO_MODE: { + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_data->echo_mode = true; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO mode"); + break; + } + + case NFCV_CMD_CUST_ECHO_DATA: { + nfcv_emu_send( + tx_rx, + nfcv_data, + &nfcv_data->frame[ctx->payload_offset], + nfcv_data->frame_length - ctx->payload_offset - 2, + NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data"); + break; + } + + default: + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "unsupported: %02X", + ctx->command); + break; + } + + if(strlen(nfcv_data->last_command) > 0) { +#ifdef NFCV_VERBOSE + FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command); +#endif + } +} + +void nfcv_emu_sniff_packet( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + + if(nfcv_data->frame_length < 2) { + return; + } + + /* parse the frame data for the upcoming part 3 handling */ + ctx->flags = nfcv_data->frame[0]; + ctx->command = nfcv_data->frame[1]; + ctx->selected = (ctx->flags & NFCV_REQ_FLAG_SELECT); + ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && + (ctx->flags & NFCV_REQ_FLAG_ADDRESS); + ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED); + ctx->address_offset = 2 + (ctx->advanced ? 1 : 0); + ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0); + + char flags_string[5]; + + snprintf( + flags_string, + 5, + "%c%c%c%d", + (ctx->flags & NFCV_REQ_FLAG_INVENTORY) ? + 'I' : + (ctx->addressed ? 'A' : (ctx->selected ? 'S' : '*')), + ctx->advanced ? 'X' : ' ', + (ctx->flags & NFCV_REQ_FLAG_DATA_RATE) ? 'h' : 'l', + (ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) ? 2 : 1); + + switch(ctx->command) { + case NFCV_CMD_INVENTORY: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s INVENTORY", flags_string); + break; + } + + case NFCV_CMD_STAY_QUIET: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s STAYQUIET", flags_string); + nfcv_data->quiet = true; + break; + } + + case NFCV_CMD_LOCK_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s LOCK %d", + flags_string, + block); + break; + } + + case NFCV_CMD_WRITE_DSFID: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WR DSFID %d", + flags_string, + id); + break; + } + + case NFCV_CMD_WRITE_AFI: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WR AFI %d", + flags_string, + id); + break; + } + + case NFCV_CMD_LOCK_DSFID: { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s LOCK DSFID", + flags_string); + break; + } + + case NFCV_CMD_LOCK_AFI: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s LOCK AFI", flags_string); + break; + } + + case NFCV_CMD_SELECT: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s SELECT", flags_string); + break; + } + + case NFCV_CMD_RESET_TO_READY: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s RESET", flags_string); + break; + } + + case NFCV_CMD_READ_MULTI_BLOCK: + case NFCV_CMD_READ_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t blocks = 1; + + if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { + blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; + } + + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s READ %d cnt: %d", + flags_string, + block, + blocks); + + break; + } + + case NFCV_CMD_WRITE_MULTI_BLOCK: + case NFCV_CMD_WRITE_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t blocks = 1; + uint8_t data_pos = 1; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; + data_pos++; + } + + uint8_t* data = &nfcv_data->frame[ctx->payload_offset + data_pos]; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WRITE %d, cnd %d", + flags_string, + block, + blocks); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WRITE %d %02X %02X %02X %02X", + flags_string, + block, + data[0], + data[1], + data[2], + data[3]); + } + break; + } + + case NFCV_CMD_GET_SYSTEM_INFO: { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s SYSTEMINFO", + flags_string); + break; + } + + default: + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s unsupported: %02X", + flags_string, + ctx->command); + break; + } + + if(strlen(nfcv_data->last_command) > 0) { + FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command); + } +} + +void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + furi_assert(nfc_data); + furi_assert(nfcv_data); + + if(!nfcv_emu_alloc(nfcv_data)) { + FURI_LOG_E(TAG, "Failed to allocate structures"); + nfcv_data->ready = false; + return; + } + + strcpy(nfcv_data->last_command, ""); + nfcv_data->quiet = false; + nfcv_data->selected = false; + nfcv_data->modified = false; + + /* everything is initialized */ + nfcv_data->ready = true; + + /* ensure the GPIO is already in unmodulated state */ + furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); + + rfal_platform_spi_acquire(); + /* stop operation to configure for transparent and passive mode */ + st25r3916ExecuteCommand(ST25R3916_CMD_STOP); + /* set enable, rx_enable and field detector enable */ + st25r3916WriteRegister( + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); + /* explicitely set the modulation resistor in case system config changes for some reason */ + st25r3916WriteRegister( + ST25R3916_REG_PT_MOD, + (0 << ST25R3916_REG_PT_MOD_ptm_res_shift) | (15 << ST25R3916_REG_PT_MOD_pt_res_shift)); + /* target mode: target, other fields do not have any effect as we use transparent mode */ + st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ); + /* let us modulate the field using MOSI, read ASK modulation using IRQ */ + st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE); + + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); + + /* if not set already, initialize the default protocol handler */ + if(!nfcv_data->emu_protocol_ctx) { + nfcv_data->emu_protocol_ctx = malloc(sizeof(NfcVEmuProtocolCtx)); + if(nfcv_data->sub_type == NfcVTypeSniff) { + nfcv_data->emu_protocol_handler = &nfcv_emu_sniff_packet; + } else { + nfcv_data->emu_protocol_handler = &nfcv_emu_handle_packet; + } + } + + FURI_LOG_D(TAG, "Starting NfcV emulation"); + FURI_LOG_D( + TAG, + " UID: %02X %02X %02X %02X %02X %02X %02X %02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); + + switch(nfcv_data->sub_type) { + case NfcVTypeSlixL: + FURI_LOG_D(TAG, " Card type: SLIX-L"); + slix_l_prepare(nfcv_data); + break; + case NfcVTypeSlixS: + FURI_LOG_D(TAG, " Card type: SLIX-S"); + slix_s_prepare(nfcv_data); + break; + case NfcVTypeSlix2: + FURI_LOG_D(TAG, " Card type: SLIX2"); + slix2_prepare(nfcv_data); + break; + case NfcVTypeSlix: + FURI_LOG_D(TAG, " Card type: SLIX"); + slix_prepare(nfcv_data); + break; + case NfcVTypePlain: + FURI_LOG_D(TAG, " Card type: Plain"); + break; + case NfcVTypeSniff: + FURI_LOG_D(TAG, " Card type: Sniffing"); + break; + } + + /* allocate a 512 edge buffer, more than enough */ + nfcv_data->emu_air.reader_signal = + pulse_reader_alloc(&gpio_nfc_irq_rfid_pull, NFCV_PULSE_BUFFER); + /* timebase shall be 1 ns */ + pulse_reader_set_timebase(nfcv_data->emu_air.reader_signal, PulseReaderUnitNanosecond); + /* and configure to already calculate the number of bits */ + pulse_reader_set_bittime(nfcv_data->emu_air.reader_signal, NFCV_PULSE_DURATION_NS); + /* this IO is fed into the µC via a diode, so we need a pulldown */ + pulse_reader_set_pull(nfcv_data->emu_air.reader_signal, GpioPullDown); + + /* start sampling */ + pulse_reader_start(nfcv_data->emu_air.reader_signal); +} + +void nfcv_emu_deinit(NfcVData* nfcv_data) { + furi_assert(nfcv_data); + + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); + nfcv_emu_free(nfcv_data); + + if(nfcv_data->emu_protocol_ctx) { + free(nfcv_data->emu_protocol_ctx); + nfcv_data->emu_protocol_ctx = NULL; + } + + /* set registers back to how we found them */ + st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, 0x00); + st25r3916WriteRegister(ST25R3916_REG_MODE, 0x08); + rfal_platform_spi_release(); +} + +bool nfcv_emu_loop( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + NfcVData* nfcv_data, + uint32_t timeout_ms) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data); + + bool ret = false; + uint32_t frame_state = NFCV_FRAME_STATE_SOF1; + uint32_t periods_previous = 0; + uint32_t frame_pos = 0; + uint32_t byte_value = 0; + uint32_t bits_received = 0; + uint32_t timeout = timeout_ms * 1000; + bool wait_for_pulse = false; + + if(!nfcv_data->ready) { + return false; + } + +#ifdef NFCV_DIAGNOSTIC_DUMPS + uint8_t period_buffer[NFCV_DIAGNOSTIC_DUMP_SIZE]; + uint32_t period_buffer_pos = 0; +#endif + + while(true) { + uint32_t periods = pulse_reader_receive(nfcv_data->emu_air.reader_signal, timeout); + uint32_t timestamp = DWT->CYCCNT; + + /* when timed out, reset to SOF state */ + if(periods == PULSE_READER_NO_EDGE || periods == PULSE_READER_LOST_EDGE) { + break; + } + +#ifdef NFCV_DIAGNOSTIC_DUMPS + if(period_buffer_pos < sizeof(period_buffer)) { + period_buffer[period_buffer_pos++] = periods; + } +#endif + + /* short helper for detecting a pulse position */ + if(wait_for_pulse) { + wait_for_pulse = false; + if(periods != 1) { + frame_state = NFCV_FRAME_STATE_RESET; + } + continue; + } + + switch(frame_state) { + case NFCV_FRAME_STATE_SOF1: + if(periods == 1) { + frame_state = NFCV_FRAME_STATE_SOF2; + } else { + frame_state = NFCV_FRAME_STATE_SOF1; + break; + } + break; + + case NFCV_FRAME_STATE_SOF2: + /* waiting for the second low period, telling us about coding */ + if(periods == 6) { + frame_state = NFCV_FRAME_STATE_CODING_256; + periods_previous = 0; + wait_for_pulse = true; + } else if(periods == 4) { + frame_state = NFCV_FRAME_STATE_CODING_4; + periods_previous = 2; + wait_for_pulse = true; + } else { + frame_state = NFCV_FRAME_STATE_RESET; + } + break; + + case NFCV_FRAME_STATE_CODING_256: + if(periods_previous > periods) { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + /* previous symbol left us with some pulse periods */ + periods -= periods_previous; + + if(periods > 512) { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } else if(periods == 2) { + frame_state = NFCV_FRAME_STATE_EOF; + break; + } + + periods_previous = 512 - (periods + 1); + byte_value = (periods - 1) / 2; + if(frame_pos < NFCV_FRAMESIZE_MAX) { + nfcv_data->frame[frame_pos++] = (uint8_t)byte_value; + } + + wait_for_pulse = true; + + break; + + case NFCV_FRAME_STATE_CODING_4: + if(periods_previous > periods) { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + /* previous symbol left us with some pulse periods */ + periods -= periods_previous; + periods_previous = 0; + + byte_value >>= 2; + bits_received += 2; + + if(periods == 1) { + byte_value |= 0x00 << 6; + periods_previous = 6; + } else if(periods == 3) { + byte_value |= 0x01 << 6; + periods_previous = 4; + } else if(periods == 5) { + byte_value |= 0x02 << 6; + periods_previous = 2; + } else if(periods == 7) { + byte_value |= 0x03 << 6; + periods_previous = 0; + } else if(periods == 2) { + frame_state = NFCV_FRAME_STATE_EOF; + break; + } else { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + if(bits_received >= 8) { + if(frame_pos < NFCV_FRAMESIZE_MAX) { + nfcv_data->frame[frame_pos++] = (uint8_t)byte_value; + } + bits_received = 0; + } + wait_for_pulse = true; + break; + } + + /* post-state-machine cleanup and reset */ + if(frame_state == NFCV_FRAME_STATE_RESET) { + frame_state = NFCV_FRAME_STATE_SOF1; + } else if(frame_state == NFCV_FRAME_STATE_EOF) { + nfcv_data->frame_length = frame_pos; + nfcv_data->eof_timestamp = timestamp; + break; + } + } + + if(frame_state == NFCV_FRAME_STATE_EOF) { + /* we know that this code uses TIM2, so stop pulse reader */ + pulse_reader_stop(nfcv_data->emu_air.reader_signal); + if(tx_rx->sniff_rx) { + tx_rx->sniff_rx(nfcv_data->frame, frame_pos * 8, false, tx_rx->sniff_context); + } + nfcv_data->emu_protocol_handler(tx_rx, nfc_data, nfcv_data); + + pulse_reader_start(nfcv_data->emu_air.reader_signal); + ret = true; + } else { + if(frame_state != NFCV_FRAME_STATE_SOF1) { +#ifdef NFCV_VERBOSE + FURI_LOG_T(TAG, "leaving while in state: %lu", frame_state); +#endif + } + } + +#ifdef NFCV_DIAGNOSTIC_DUMPS + if(period_buffer_pos) { + FURI_LOG_T(TAG, "pulses:"); + for(uint32_t pos = 0; pos < period_buffer_pos; pos++) { + FURI_LOG_T(TAG, " #%lu: %u", pos, period_buffer[pos]); + } + } +#endif + + return ret; +} diff --git a/lib/nfc/protocols/nfcv.h b/lib/nfc/protocols/nfcv.h new file mode 100644 index 000000000..8ca6955d1 --- /dev/null +++ b/lib/nfc/protocols/nfcv.h @@ -0,0 +1,291 @@ +#pragma once + +#include +#include + +#include +#include +#include "nfc_util.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* true: modulating releases load, false: modulating adds load resistor to field coil */ +#define NFCV_LOAD_MODULATION_POLARITY (false) + +#define NFCV_FC (13560000.0f / 0.9998f) /* MHz */ +#define NFCV_RESP_SUBC1_PULSE_32 (1.0f / (NFCV_FC / 32) / 2.0f) /* 1.1799 µs */ +#define NFCV_RESP_SUBC1_UNMOD_256 (256.0f / NFCV_FC) /* 18.8791 µs */ +#define NFCV_PULSE_DURATION_NS (128.0f * 1000000000.0f / NFCV_FC) + +/* ISO/IEC 15693-3:2019(E) 10.4.12: maximum number of blocks is defined as 256 */ +#define NFCV_BLOCKS_MAX 256 +/* ISO/IEC 15693-3:2019(E) 10.4.12: maximum size of blocks is defined as 32 */ +#define NFCV_BLOCKSIZE_MAX 32 +/* the resulting memory size a card can have */ +#define NFCV_MEMSIZE_MAX (NFCV_BLOCKS_MAX * NFCV_BLOCKSIZE_MAX) +/* ISO/IEC 15693-3:2019(E) 7.1b: standard allows up to 8192, the maxium frame length that we are expected to receive/send is less */ +#define NFCV_FRAMESIZE_MAX (1 + NFCV_MEMSIZE_MAX + NFCV_BLOCKS_MAX) + +/* maximum string length for log messages */ +#define NFCV_LOG_STR_LEN 128 +/* maximum of pulses to be buffered by pulse reader */ +#define NFCV_PULSE_BUFFER 512 + +//#define NFCV_DIAGNOSTIC_DUMPS +//#define NFCV_DIAGNOSTIC_DUMP_SIZE 256 +//#define NFCV_VERBOSE + +/* helpers to calculate the send time based on DWT->CYCCNT */ +#define NFCV_FDT_USEC(usec) ((usec)*64) +#define NFCV_FDT_FC(ticks) ((ticks)*6400 / 1356) + +/* state machine when receiving frame bits */ +#define NFCV_FRAME_STATE_SOF1 0 +#define NFCV_FRAME_STATE_SOF2 1 +#define NFCV_FRAME_STATE_CODING_4 2 +#define NFCV_FRAME_STATE_CODING_256 3 +#define NFCV_FRAME_STATE_EOF 4 +#define NFCV_FRAME_STATE_RESET 5 + +/* sequences for every section of a frame */ +#define NFCV_SIG_SOF 0 +#define NFCV_SIG_BIT0 1 +#define NFCV_SIG_BIT1 2 +#define NFCV_SIG_EOF 3 +#define NFCV_SIG_LOW_SOF 4 +#define NFCV_SIG_LOW_BIT0 5 +#define NFCV_SIG_LOW_BIT1 6 +#define NFCV_SIG_LOW_EOF 7 + +/* various constants */ +#define NFCV_COMMAND_RETRIES 5 +#define NFCV_UID_LENGTH 8 + +/* ISO15693 protocol flags */ +typedef enum { + /* ISO15693 protocol flags when INVENTORY is NOT set */ + NFCV_REQ_FLAG_SUB_CARRIER = (1 << 0), + NFCV_REQ_FLAG_DATA_RATE = (1 << 1), + NFCV_REQ_FLAG_INVENTORY = (1 << 2), + NFCV_REQ_FLAG_PROTOCOL_EXT = (1 << 3), + NFCV_REQ_FLAG_SELECT = (1 << 4), + NFCV_REQ_FLAG_ADDRESS = (1 << 5), + NFCV_REQ_FLAG_OPTION = (1 << 6), + /* ISO15693 protocol flags when INVENTORY flag is set */ + NFCV_REQ_FLAG_AFI = (1 << 4), + NFCV_REQ_FLAG_NB_SLOTS = (1 << 5) +} NfcVRequestFlags; + +/* ISO15693 protocol flags */ +typedef enum { + NFCV_RES_FLAG_ERROR = (1 << 0), + NFCV_RES_FLAG_VALIDITY = (1 << 1), + NFCV_RES_FLAG_FINAL = (1 << 2), + NFCV_RES_FLAG_PROTOCOL_EXT = (1 << 3), + NFCV_RES_FLAG_SEC_LEN1 = (1 << 4), + NFCV_RES_FLAG_SEC_LEN2 = (1 << 5), + NFCV_RES_FLAG_WAIT_EXT = (1 << 6), +} NfcVRsponseFlags; + +/* flags for SYSINFO response */ +typedef enum { + NFCV_SYSINFO_FLAG_DSFID = (1 << 0), + NFCV_SYSINFO_FLAG_AFI = (1 << 1), + NFCV_SYSINFO_FLAG_MEMSIZE = (1 << 2), + NFCV_SYSINFO_FLAG_ICREF = (1 << 3) +} NfcVSysinfoFlags; + +/* ISO15693 command codes */ +typedef enum { + /* mandatory command codes */ + NFCV_CMD_INVENTORY = 0x01, + NFCV_CMD_STAY_QUIET = 0x02, + /* optional command codes */ + NFCV_CMD_READ_BLOCK = 0x20, + NFCV_CMD_WRITE_BLOCK = 0x21, + NFCV_CMD_LOCK_BLOCK = 0x22, + NFCV_CMD_READ_MULTI_BLOCK = 0x23, + NFCV_CMD_WRITE_MULTI_BLOCK = 0x24, + NFCV_CMD_SELECT = 0x25, + NFCV_CMD_RESET_TO_READY = 0x26, + NFCV_CMD_WRITE_AFI = 0x27, + NFCV_CMD_LOCK_AFI = 0x28, + NFCV_CMD_WRITE_DSFID = 0x29, + NFCV_CMD_LOCK_DSFID = 0x2A, + NFCV_CMD_GET_SYSTEM_INFO = 0x2B, + NFCV_CMD_READ_MULTI_SECSTATUS = 0x2C, + /* advanced command codes */ + NFCV_CMD_ADVANCED = 0xA0, + /* flipper zero custom command codes */ + NFCV_CMD_CUST_ECHO_MODE = 0xDE, + NFCV_CMD_CUST_ECHO_DATA = 0xDF +} NfcVCommands; + +/* ISO15693 Response error codes */ +typedef enum { + NFCV_NOERROR = 0x00, + NFCV_ERROR_CMD_NOT_SUP = 0x01, // Command not supported + NFCV_ERROR_CMD_NOT_REC = 0x02, // Command not recognized (eg. parameter error) + NFCV_ERROR_CMD_OPTION = 0x03, // Command option not supported + NFCV_ERROR_GENERIC = 0x0F, // No additional Info about this error + NFCV_ERROR_BLOCK_UNAVAILABLE = 0x10, + NFCV_ERROR_BLOCK_LOCKED_ALREADY = 0x11, // cannot lock again + NFCV_ERROR_BLOCK_LOCKED = 0x12, // cannot be changed + NFCV_ERROR_BLOCK_WRITE = 0x13, // Writing was unsuccessful + NFCV_ERROR_BLOCL_WRITELOCK = 0x14 // Locking was unsuccessful +} NfcVErrorcodes; + +typedef enum { + NfcVLockBitDsfid = 1, + NfcVLockBitAfi = 2, +} NfcVLockBits; + +typedef enum { + NfcVAuthMethodManual, + NfcVAuthMethodTonieBox, +} NfcVAuthMethod; + +typedef enum { + NfcVTypePlain = 0, + NfcVTypeSlix = 1, + NfcVTypeSlixS = 2, + NfcVTypeSlixL = 3, + NfcVTypeSlix2 = 4, + NfcVTypeSniff = 255, +} NfcVSubtype; + +typedef enum { + NfcVSendFlagsNormal = 0, + NfcVSendFlagsSof = 1 << 0, + NfcVSendFlagsCrc = 1 << 1, + NfcVSendFlagsEof = 1 << 2, + NfcVSendFlagsOneSubcarrier = 0, + NfcVSendFlagsTwoSubcarrier = 1 << 3, + NfcVSendFlagsLowRate = 0, + NfcVSendFlagsHighRate = 1 << 4 +} NfcVSendFlags; + +typedef struct { + uint8_t key_read[4]; + uint8_t key_write[4]; + uint8_t key_privacy[4]; + uint8_t key_destroy[4]; + uint8_t key_eas[4]; + uint8_t rand[2]; + bool privacy; +} NfcVSlixData; + +typedef union { + NfcVSlixData slix; +} NfcVSubtypeData; + +typedef struct { + DigitalSignal* nfcv_resp_sof; + DigitalSignal* nfcv_resp_one; + DigitalSignal* nfcv_resp_zero; + DigitalSignal* nfcv_resp_eof; +} NfcVEmuAirSignals; + +typedef struct { + PulseReader* reader_signal; + DigitalSignal* nfcv_resp_pulse; /* pulse length, fc/32 */ + DigitalSignal* nfcv_resp_half_pulse; /* half pulse length, fc/32 */ + DigitalSignal* nfcv_resp_unmod; /* unmodulated length 256/fc */ + NfcVEmuAirSignals signals_high; + NfcVEmuAirSignals signals_low; + DigitalSequence* nfcv_signal; +} NfcVEmuAir; + +typedef void (*NfcVEmuProtocolHandler)( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data); +typedef bool (*NfcVEmuProtocolFilter)( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data); + +/* the default ISO15693 handler context */ +typedef struct { + uint8_t flags; /* ISO15693-3 flags of the header as specified */ + uint8_t command; /* ISO15693-3 command at offset 1 as specified */ + bool selected; /* ISO15693-3 flags: selected frame */ + bool addressed; /* ISO15693-3 flags: addressed frame */ + bool advanced; /* ISO15693-3 command: advanced command */ + uint8_t address_offset; /* ISO15693-3 offset of the address in frame, if addressed is set */ + uint8_t payload_offset; /* ISO15693-3 offset of the payload in frame */ + + uint8_t response_buffer[NFCV_FRAMESIZE_MAX]; /* pre-allocated response buffer */ + NfcVSendFlags response_flags; /* flags to use when sending response */ + uint32_t send_time; /* timestamp when to send the response */ + + NfcVEmuProtocolFilter emu_protocol_filter; +} NfcVEmuProtocolCtx; + +typedef struct { + /* common ISO15693 fields, being specified in ISO15693-3 */ + uint8_t dsfid; + uint8_t afi; + uint8_t ic_ref; + uint16_t block_num; + uint8_t block_size; + uint8_t data[NFCV_MEMSIZE_MAX]; + uint8_t security_status[1 + NFCV_BLOCKS_MAX]; + bool selected; + bool quiet; + + bool modified; + bool ready; + bool echo_mode; + + /* specfic variant infos */ + NfcVSubtype sub_type; + NfcVSubtypeData sub_data; + NfcVAuthMethod auth_method; + + /* precalced air level data */ + NfcVEmuAir emu_air; + + uint8_t* frame; /* [NFCV_FRAMESIZE_MAX] ISO15693-2 incoming raw data from air layer */ + uint8_t frame_length; /* ISO15693-2 length of incoming data */ + uint32_t eof_timestamp; /* ISO15693-2 EOF timestamp, read from DWT->CYCCNT */ + + /* handler for the protocol layer as specified in ISO15693-3 */ + NfcVEmuProtocolHandler emu_protocol_handler; + void* emu_protocol_ctx; + /* runtime data */ + char last_command[NFCV_LOG_STR_LEN]; + char error[NFCV_LOG_STR_LEN]; +} NfcVData; + +typedef struct { + uint16_t blocks_to_read; + int16_t blocks_read; +} NfcVReader; + +ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* data); +ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* data); +ReturnCode nfcv_inventory(uint8_t* uid); +bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* data); + +void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); +void nfcv_emu_deinit(NfcVData* nfcv_data); +bool nfcv_emu_loop( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + NfcVData* nfcv_data, + uint32_t timeout_ms); +void nfcv_emu_send( + FuriHalNfcTxRxContext* tx_rx, + NfcVData* nfcv, + uint8_t* data, + uint8_t length, + NfcVSendFlags flags, + uint32_t send_time); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix.c b/lib/nfc/protocols/slix.c new file mode 100644 index 000000000..ec3afc248 --- /dev/null +++ b/lib/nfc/protocols/slix.c @@ -0,0 +1,412 @@ + +#include +#include "nfcv.h" +#include "slix.h" +#include "nfc_util.h" +#include +#include "furi_hal_nfc.h" +#include + +#define TAG "SLIX" + +static uint32_t slix_read_be(uint8_t* data, uint32_t length) { + uint32_t value = 0; + + for(uint32_t pos = 0; pos < length; pos++) { + value <<= 8; + value |= data[pos]; + } + + return value; +} + +uint8_t slix_get_ti(FuriHalNfcDevData* nfc_data) { + return (nfc_data->uid[3] >> 3) & 3; +} + +bool slix_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) && + slix_get_ti(nfc_data) == 2) { + return true; + } + return false; +} + +bool slix2_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) && + slix_get_ti(nfc_data) == 1) { + return true; + } + return false; +} + +bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x02)) { + return true; + } + return false; +} + +bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x03)) { + return true; + } + return false; +} + +ReturnCode slix_get_random(NfcVData* data) { + uint16_t received = 0; + uint8_t rxBuf[32]; + + ReturnCode ret = rfalNfcvPollerTransceiveReq( + NFCV_CMD_NXP_GET_RANDOM_NUMBER, + RFAL_NFCV_REQ_FLAG_DEFAULT, + NFCV_MANUFACTURER_NXP, + NULL, + NULL, + 0, + rxBuf, + sizeof(rxBuf), + &received); + + if(ret == ERR_NONE) { + if(received != 3) { + return ERR_PROTO; + } + if(data != NULL) { + data->sub_data.slix.rand[0] = rxBuf[2]; + data->sub_data.slix.rand[1] = rxBuf[1]; + } + } + + return ret; +} + +ReturnCode slix_unlock(NfcVData* data, uint32_t password_id) { + furi_assert(rand); + + uint16_t received = 0; + uint8_t rxBuf[32]; + uint8_t cmd_set_pass[] = { + password_id, + data->sub_data.slix.rand[1], + data->sub_data.slix.rand[0], + data->sub_data.slix.rand[1], + data->sub_data.slix.rand[0]}; + uint8_t* password = NULL; + + switch(password_id) { + case SLIX_PASS_READ: + password = data->sub_data.slix.key_read; + break; + case SLIX_PASS_WRITE: + password = data->sub_data.slix.key_write; + break; + case SLIX_PASS_PRIVACY: + password = data->sub_data.slix.key_privacy; + break; + case SLIX_PASS_DESTROY: + password = data->sub_data.slix.key_destroy; + break; + case SLIX_PASS_EASAFI: + password = data->sub_data.slix.key_eas; + break; + default: + break; + } + + if(!password) { + return ERR_NOTSUPP; + } + + for(int pos = 0; pos < 4; pos++) { + cmd_set_pass[1 + pos] ^= password[3 - pos]; + } + + ReturnCode ret = rfalNfcvPollerTransceiveReq( + NFCV_CMD_NXP_SET_PASSWORD, + RFAL_NFCV_REQ_FLAG_DATA_RATE, + NFCV_MANUFACTURER_NXP, + NULL, + cmd_set_pass, + sizeof(cmd_set_pass), + rxBuf, + sizeof(rxBuf), + &received); + + return ret; +} + +bool slix_generic_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in, + uint32_t password_supported) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + NfcVSlixData* slix = &nfcv_data->sub_data.slix; + + if(slix->privacy && ctx->command != NFCV_CMD_NXP_GET_RANDOM_NUMBER && + ctx->command != NFCV_CMD_NXP_SET_PASSWORD) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "command 0x%02X ignored, privacy mode", + ctx->command); + FURI_LOG_D(TAG, "%s", nfcv_data->last_command); + return true; + } + + bool handled = false; + + switch(ctx->command) { + case NFCV_CMD_NXP_GET_RANDOM_NUMBER: { + slix->rand[0] = furi_hal_random_get(); + slix->rand[1] = furi_hal_random_get(); + + ctx->response_buffer[0] = NFCV_NOERROR; + ctx->response_buffer[1] = slix->rand[1]; + ctx->response_buffer[2] = slix->rand[0]; + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 3, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "GET_RANDOM_NUMBER -> 0x%02X%02X", + slix->rand[0], + slix->rand[1]); + + handled = true; + break; + } + + case NFCV_CMD_NXP_SET_PASSWORD: { + uint8_t password_id = nfcv_data->frame[ctx->payload_offset]; + + if(!(password_id & password_supported)) { + break; + } + + uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1]; + uint8_t* rand = slix->rand; + uint8_t* password = NULL; + uint8_t password_rcv[4]; + + switch(password_id) { + case SLIX_PASS_READ: + password = slix->key_read; + break; + case SLIX_PASS_WRITE: + password = slix->key_write; + break; + case SLIX_PASS_PRIVACY: + password = slix->key_privacy; + break; + case SLIX_PASS_DESTROY: + password = slix->key_destroy; + break; + case SLIX_PASS_EASAFI: + password = slix->key_eas; + break; + default: + break; + } + + if(!password) { + break; + } + + for(int pos = 0; pos < 4; pos++) { + password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2]; + } + uint32_t pass_expect = slix_read_be(password, 4); + uint32_t pass_received = slix_read_be(password_rcv, 4); + + /* if the password is all-zeroes, just accept any password*/ + if(!pass_expect || pass_expect == pass_received) { + switch(password_id) { + case SLIX_PASS_READ: + break; + case SLIX_PASS_WRITE: + break; + case SLIX_PASS_PRIVACY: + slix->privacy = false; + nfcv_data->modified = true; + break; + case SLIX_PASS_DESTROY: + FURI_LOG_D(TAG, "Pooof! Got destroyed"); + break; + case SLIX_PASS_EASAFI: + break; + default: + break; + } + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "SET_PASSWORD #%02X 0x%08lX OK", + password_id, + pass_received); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "SET_PASSWORD #%02X 0x%08lX/%08lX FAIL", + password_id, + pass_received, + pass_expect); + } + handled = true; + break; + } + + case NFCV_CMD_NXP_ENABLE_PRIVACY: { + ctx->response_buffer[0] = NFCV_NOERROR; + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "NFCV_CMD_NXP_ENABLE_PRIVACY"); + + slix->privacy = true; + handled = true; + break; + } + } + + return handled; +} + +bool slix_l_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter( + tx_rx, + nfc_data, + nfcv_data_in, + SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI)) { + return true; + } + + return handled; +} + +void slix_l_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix_l_protocol_filter; +} + +bool slix_s_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) { + return true; + } + + return handled; +} + +void slix_s_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix_s_protocol_filter; +} + +bool slix_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_EASAFI)) { + return true; + } + + return handled; +} + +void slix_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix_protocol_filter; +} + +bool slix2_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) { + return true; + } + + return handled; +} + +void slix2_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix2_protocol_filter; +} diff --git a/lib/nfc/protocols/slix.h b/lib/nfc/protocols/slix.h new file mode 100644 index 000000000..701fa2f82 --- /dev/null +++ b/lib/nfc/protocols/slix.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include "nfc_util.h" +#include + +#define NFCV_MANUFACTURER_NXP 0x04 + +/* ISO15693-3 CUSTOM NXP COMMANDS */ +#define NFCV_CMD_NXP_SET_EAS 0xA2 +#define NFCV_CMD_NXP_RESET_EAS 0xA3 +#define NFCV_CMD_NXP_LOCK_EAS 0xA4 +#define NFCV_CMD_NXP_EAS_ALARM 0xA5 +#define NFCV_CMD_NXP_PASSWORD_PROTECT_EAS_AFI 0xA6 +#define NFCV_CMD_NXP_WRITE_EAS_ID 0xA7 +#define NFCV_CMD_NXP_INVENTORY_PAGE_READ 0xB0 +#define NFCV_CMD_NXP_INVENTORY_PAGE_READ_FAST 0xB1 +#define NFCV_CMD_NXP_GET_RANDOM_NUMBER 0xB2 +#define NFCV_CMD_NXP_SET_PASSWORD 0xB3 +#define NFCV_CMD_NXP_WRITE_PASSWORD 0xB4 +#define NFCV_CMD_NXP_DESTROY 0xB9 +#define NFCV_CMD_NXP_ENABLE_PRIVACY 0xBA + +/* available passwords */ +#define SLIX_PASS_READ 0x01 +#define SLIX_PASS_WRITE 0x02 +#define SLIX_PASS_PRIVACY 0x04 +#define SLIX_PASS_DESTROY 0x08 +#define SLIX_PASS_EASAFI 0x10 + +#define SLIX_PASS_ALL \ + (SLIX_PASS_READ | SLIX_PASS_WRITE | SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI) + +bool slix_check_card_type(FuriHalNfcDevData* nfc_data); +bool slix2_check_card_type(FuriHalNfcDevData* nfc_data); +bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data); +bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data); + +ReturnCode slix_get_random(NfcVData* data); +ReturnCode slix_unlock(NfcVData* data, uint32_t password_id); + +void slix_prepare(NfcVData* nfcv_data); +void slix_s_prepare(NfcVData* nfcv_data); +void slix_l_prepare(NfcVData* nfcv_data); +void slix2_prepare(NfcVData* nfcv_data); From d620285e0ae51f1b2d414643d19d01e02fde92f2 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 19:52:01 +0300 Subject: [PATCH 12/40] OFW PR 2669: nfc: Fix MFUL tearing flags read by GMMan --- lib/nfc/protocols/mifare_ultralight.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c index 0e28c0074..6960bd2a1 100644 --- a/lib/nfc/protocols/mifare_ultralight.c +++ b/lib/nfc/protocols/mifare_ultralight.c @@ -702,7 +702,7 @@ bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralight FURI_LOG_D(TAG, "Reading tearing flags"); for(size_t i = 0; i < 3; i++) { tx_rx->tx_data[0] = MF_UL_CHECK_TEARING; - tx_rx->rx_data[1] = i; + tx_rx->tx_data[1] = i; tx_rx->tx_bits = 16; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { From c9ddecff87419e5cf7aec4e29404c1cafcf46943 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 21:18:05 +0300 Subject: [PATCH 13/40] OFW PR 2658: Hitag by blackvault88 --- applications/main/lfrfid/lfrfid.c | 15 +- applications/main/lfrfid/lfrfid_i.h | 2 + .../scenes/lfrfid_scene_extra_actions.c | 12 + .../main/lfrfid/scenes/lfrfid_scene_read.c | 18 + .../lfrfid/scenes/lfrfid_scene_save_type.c | 3 + .../lfrfid/scenes/lfrfid_scene_saved_info.c | 12 +- .../main/lfrfid/views/lfrfid_view_read.c | 75 +- .../main/lfrfid/views/lfrfid_view_read.h | 11 +- .../services/gui/modules/byte_input.c | 14 +- .../services/gui/modules/byte_input.h | 2 +- firmware/targets/f7/api_symbols.csv | 2 +- lib/lfrfid/lfrfid_dict_file.c | 82 +- lib/lfrfid/lfrfid_hitag_worker.c | 2370 +++++++++++++++++ lib/lfrfid/lfrfid_hitag_worker.h | 86 + lib/lfrfid/lfrfid_worker.h | 3 + lib/lfrfid/lfrfid_worker_i.h | 1 + lib/lfrfid/lfrfid_worker_modes.c | 120 +- lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 2 + lib/lfrfid/protocols/protocol_hitag1.c | 104 + lib/lfrfid/protocols/protocol_hitag1.h | 4 + 21 files changed, 2893 insertions(+), 47 deletions(-) create mode 100644 lib/lfrfid/lfrfid_hitag_worker.c create mode 100644 lib/lfrfid/lfrfid_hitag_worker.h create mode 100644 lib/lfrfid/protocols/protocol_hitag1.c create mode 100644 lib/lfrfid/protocols/protocol_hitag1.h diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index a2bcdcf52..a82d2dd03 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -186,11 +186,16 @@ int32_t lfrfid_app(void* p) { DOLPHIN_DEED(DolphinDeedRfidEmulate); } else { furi_string_set(app->file_path, args); - lfrfid_load_key_data(app, app->file_path, true); - view_dispatcher_attach_to_gui( - app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); - DOLPHIN_DEED(DolphinDeedRfidEmulate); + if(lfrfid_load_key_data(app, app->file_path, true)) { + view_dispatcher_attach_to_gui( + app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); + DOLPHIN_DEED(DolphinDeedRfidEmulate); + } else { + // TODO: exit properly + lfrfid_free(app); + return 0; + } } } else { diff --git a/applications/main/lfrfid/lfrfid_i.h b/applications/main/lfrfid/lfrfid_i.h index 86929a14a..a883d1926 100644 --- a/applications/main/lfrfid/lfrfid_i.h +++ b/applications/main/lfrfid/lfrfid_i.h @@ -56,6 +56,8 @@ enum LfRfidCustomEvent { LfRfidEventReadSenseCardEnd, LfRfidEventReadStartASK, LfRfidEventReadStartPSK, + LfRfidEventReadStartRTF, + LfRfidEventReadSenseHitag, //TODO combine with sensecardstart? LfRfidEventReadDone, LfRfidEventReadOverrun, LfRfidEventReadError, diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c index d2bf20680..53bf32bbd 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c @@ -4,6 +4,7 @@ typedef enum { SubmenuIndexASK, SubmenuIndexPSK, + SubmenuIndexHitag, SubmenuIndexClearT5577, SubmenuIndexRAW, SubmenuIndexRAWEmulate, @@ -31,6 +32,12 @@ void lfrfid_scene_extra_actions_on_enter(void* context) { SubmenuIndexPSK, lfrfid_scene_extra_actions_submenu_callback, app); + submenu_add_item( + submenu, + "Read RTF (Reader Talks First)", + SubmenuIndexHitag, + lfrfid_scene_extra_actions_submenu_callback, + app); submenu_add_item( submenu, "Clear T5577 Password", @@ -79,6 +86,11 @@ bool lfrfid_scene_extra_actions_on_event(void* context, SceneManagerEvent event) scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); DOLPHIN_DEED(DolphinDeedRfidRead); consumed = true; + } else if(event.event == SubmenuIndexHitag) { + app->read_type = LFRFIDWorkerReadTypeRTFOnly; + scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); + DOLPHIN_DEED(DolphinDeedRfidRead); + consumed = true; } else if(event.event == SubmenuIndexClearT5577) { scene_manager_next_scene(app->scene_manager, LfRfidSceneClearT5577Confirm); consumed = true; diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read.c b/applications/main/lfrfid/scenes/lfrfid_scene_read.c index 5f1959728..e9168c818 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read.c @@ -36,6 +36,10 @@ static void event = LfRfidEventReadStartASK; } else if(result == LFRFIDWorkerReadStartPSK) { event = LfRfidEventReadStartPSK; + } else if(result == LFRFIDWorkerReadStartRTF) { + event = LfRfidEventReadStartRTF; + } else if(result == LFRFIDWorkerReadSenseHitag) { //TODO combine with sensecardstart? + event = LfRfidEventReadSenseHitag; } else { return; } @@ -50,6 +54,9 @@ void lfrfid_scene_read_on_enter(void* context) { lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadPskOnly); } else if(app->read_type == LFRFIDWorkerReadTypeASKOnly) { lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadAskOnly); + } else if(app->read_type == LFRFIDWorkerReadTypeRTFOnly) { + lfrfid_view_read_set_read_state(app->read_view, LfRfidReadScanning); + lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadRtfOnly); } lfrfid_worker_start_thread(app->lfworker); @@ -93,6 +100,17 @@ bool lfrfid_scene_read_on_event(void* context, SceneManagerEvent event) { lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadAsk); } consumed = true; + } else if(event.event == LfRfidEventReadStartRTF) { + if(app->read_type == LFRFIDWorkerReadTypeAuto) { + lfrfid_view_read_set_read_state(app->read_view, LfRfidReadScanning); + lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadHitag); + } + consumed = true; + } else if(event.event == LfRfidEventReadSenseHitag) { //TODO combine with sensecardstart? + if(app->read_type == LFRFIDWorkerReadTypeAuto || + app->read_type == LFRFIDWorkerReadTypeRTFOnly) { + lfrfid_view_read_set_read_state(app->read_view, LfRfidReadTagDetected); + } } } diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c index eb73b1123..f7bd6c6e5 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c @@ -17,6 +17,9 @@ void lfrfid_scene_save_type_on_enter(void* context) { SaveTypeCtx* state = malloc(sizeof(SaveTypeCtx)); FuriString* protocol_string = furi_string_alloc(); for(uint8_t i = 0; i < LFRFIDProtocolMax; i++) { + if(i == LFRFIDProtocolHitag1) { + continue; + } if((strcmp( protocol_dict_get_manufacturer(app->dict, i), protocol_dict_get_name(app->dict, i)) != 0) && diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c b/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c index c901629c3..812450314 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c @@ -17,11 +17,15 @@ void lfrfid_scene_saved_info_on_enter(void* context) { uint8_t* data = (uint8_t*)malloc(size); protocol_dict_get_data(app->dict, app->protocol_id, data, size); for(uint8_t i = 0; i < size; i++) { - if(i != 0) { - furi_string_cat_printf(tmp_string, ":"); + if(i >= 18) { + furi_string_cat_printf(tmp_string, ".."); + break; + } else { + if(i != 0) { + furi_string_cat_printf(tmp_string, ":"); + } + furi_string_cat_printf(tmp_string, "%02X", data[i]); } - - furi_string_cat_printf(tmp_string, "%02X", data[i]); } free(data); diff --git a/applications/main/lfrfid/views/lfrfid_view_read.c b/applications/main/lfrfid/views/lfrfid_view_read.c index 094afb617..c0dda4178 100644 --- a/applications/main/lfrfid/views/lfrfid_view_read.c +++ b/applications/main/lfrfid/views/lfrfid_view_read.c @@ -11,6 +11,7 @@ struct LfRfidReadView { typedef struct { IconAnimation* icon; LfRfidReadViewMode read_mode; + LfRfidReadViewState read_state; } LfRfidReadViewModel; static void lfrfid_view_read_draw_callback(Canvas* canvas, void* _model) { @@ -22,36 +23,69 @@ static void lfrfid_view_read_draw_callback(Canvas* canvas, void* _model) { canvas_set_font(canvas, FontPrimary); if(model->read_mode == LfRfidReadAsk) { - canvas_draw_str(canvas, 70, 16, "Reading 1/2"); + canvas_draw_str(canvas, 70, 8, "Reading 1/3"); - canvas_draw_str(canvas, 77, 29, "ASK"); - canvas_draw_icon(canvas, 70, 22, &I_ButtonRight_4x7); - canvas_draw_icon_animation(canvas, 102, 21, model->icon); + canvas_draw_str(canvas, 77, 20, "ASK"); + canvas_draw_icon(canvas, 70, 13, &I_ButtonRight_4x7); + canvas_draw_icon_animation(canvas, 112, 12, model->icon); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 77, 43, "PSK"); + canvas_draw_str(canvas, 77, 33, "PSK"); + canvas_draw_str(canvas, 77, 46, "RTF"); } else if(model->read_mode == LfRfidReadPsk) { - canvas_draw_str(canvas, 70, 16, "Reading 2/2"); + canvas_draw_str(canvas, 70, 8, "Reading 2/3"); - canvas_draw_str(canvas, 77, 43, "PSK"); - canvas_draw_icon(canvas, 70, 36, &I_ButtonRight_4x7); - canvas_draw_icon_animation(canvas, 102, 35, model->icon); + canvas_draw_str(canvas, 77, 33, "PSK"); + canvas_draw_icon(canvas, 70, 26, &I_ButtonRight_4x7); + canvas_draw_icon_animation(canvas, 112, 25, model->icon); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 77, 29, "ASK"); - } else { - canvas_draw_str(canvas, 72, 16, "Reading"); + canvas_draw_str(canvas, 77, 20, "ASK"); + canvas_draw_str(canvas, 77, 46, "RTF"); + } else if(model->read_mode == LfRfidReadHitag) { + if(model->read_state == LfRfidReadScanning) { + canvas_draw_str(canvas, 70, 8, "Reading 3/3"); - if(model->read_mode == LfRfidReadAskOnly) { - canvas_draw_str(canvas, 77, 35, "ASK"); - } else { - canvas_draw_str(canvas, 77, 35, "PSK"); + canvas_draw_str(canvas, 77, 46, "RTF"); + canvas_draw_icon(canvas, 70, 39, &I_ButtonRight_4x7); + canvas_draw_icon_animation(canvas, 112, 38, model->icon); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 77, 20, "ASK"); + canvas_draw_str(canvas, 77, 33, "PSK"); + } else if(model->read_state == LfRfidReadTagDetected) { //TODO switch to other scene? + canvas_draw_str(canvas, 65, 8, "Hitag1 found"); + + canvas_set_font(canvas, FontSecondary); + //canvas_draw_str(canvas, 70, 20, "## ## ## ##"); //TODO get tag SN from hitag worker + canvas_draw_str(canvas, 70, 33, "Reading data"); + //canvas_draw_str(canvas, 70, 46, "Page: X/64"); //TODO get current page index from hitag worker + } + } else if(model->read_mode == LfRfidReadAskOnly) { + canvas_draw_str(canvas, 72, 16, "Reading"); + canvas_draw_str(canvas, 77, 35, "ASK"); + canvas_draw_icon_animation(canvas, 112, 27, model->icon); + } else if(model->read_mode == LfRfidReadPskOnly) { + canvas_draw_str(canvas, 72, 16, "Reading"); + canvas_draw_str(canvas, 77, 35, "PSK"); + canvas_draw_icon_animation(canvas, 112, 27, model->icon); + } else if(model->read_mode == LfRfidReadRtfOnly) { + if(model->read_state == LfRfidReadScanning) { + canvas_draw_str(canvas, 72, 16, "Reading"); + canvas_draw_str(canvas, 77, 35, "RTF"); + canvas_draw_icon_animation(canvas, 112, 27, model->icon); + } else if(model->read_state == LfRfidReadTagDetected) { //TODO switch to other scene? + canvas_draw_str(canvas, 65, 8, "Hitag1 found"); + + canvas_set_font(canvas, FontSecondary); + //canvas_draw_str(canvas, 70, 20, "## ## ## ##"); //TODO get tag SN from hitag worker + canvas_draw_str(canvas, 70, 33, "Reading data"); + //canvas_draw_str(canvas, 70, 46, "Page: X/64"); //TODO get current page index from hitag worker } - canvas_draw_icon_animation(canvas, 102, 27, model->icon); } canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 61, 56, "Don't move card"); + canvas_draw_str(canvas, 61, 60, "Don't move card"); } void lfrfid_view_read_enter(void* context) { @@ -111,3 +145,8 @@ void lfrfid_view_read_set_read_mode(LfRfidReadView* read_view, LfRfidReadViewMod }, true); } + +void lfrfid_view_read_set_read_state(LfRfidReadView* read_view, LfRfidReadViewState state) { + with_view_model( + read_view->view, LfRfidReadViewModel * model, { model->read_state = state; }, true); +} \ No newline at end of file diff --git a/applications/main/lfrfid/views/lfrfid_view_read.h b/applications/main/lfrfid/views/lfrfid_view_read.h index 55bb1f230..7d6d8ead4 100644 --- a/applications/main/lfrfid/views/lfrfid_view_read.h +++ b/applications/main/lfrfid/views/lfrfid_view_read.h @@ -4,10 +4,17 @@ typedef enum { LfRfidReadAsk, LfRfidReadPsk, + LfRfidReadHitag, LfRfidReadAskOnly, - LfRfidReadPskOnly + LfRfidReadPskOnly, + LfRfidReadRtfOnly, } LfRfidReadViewMode; +typedef enum { + LfRfidReadScanning, + LfRfidReadTagDetected, +} LfRfidReadViewState; + typedef struct LfRfidReadView LfRfidReadView; LfRfidReadView* lfrfid_view_read_alloc(); @@ -17,3 +24,5 @@ void lfrfid_view_read_free(LfRfidReadView* read_view); View* lfrfid_view_read_get_view(LfRfidReadView* read_view); void lfrfid_view_read_set_read_mode(LfRfidReadView* read_view, LfRfidReadViewMode mode); + +void lfrfid_view_read_set_read_state(LfRfidReadView* read_view, LfRfidReadViewState state); \ No newline at end of file diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index b2d21f7ae..f1edf9de8 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -17,17 +17,17 @@ typedef struct { typedef struct { const char* header; uint8_t* bytes; - uint8_t bytes_count; + uint16_t bytes_count; ByteInputCallback input_callback; ByteChangedCallback changed_callback; void* callback_context; bool selected_high_nibble; - uint8_t selected_byte; + uint16_t selected_byte; int8_t selected_row; // row -1 - input, row 0 & 1 - keyboard uint8_t selected_column; - uint8_t first_visible_byte; + uint16_t first_visible_byte; } ByteInputModel; static const uint8_t keyboard_origin_x = 7; @@ -164,7 +164,7 @@ static void byte_input_draw_input(Canvas* canvas, ByteInputModel* model) { canvas_draw_icon(canvas, 2, 19, &I_ButtonLeftSmall_3x5); canvas_draw_icon(canvas, 123, 19, &I_ButtonRightSmall_3x5); - for(uint8_t i = model->first_visible_byte; + for(uint16_t i = model->first_visible_byte; i < model->first_visible_byte + MIN(model->bytes_count, max_drawable_bytes); i++) { uint8_t byte_position = i - model->first_visible_byte; @@ -253,7 +253,7 @@ static void byte_input_draw_input_selected(Canvas* canvas, ByteInputModel* model canvas_draw_icon(canvas, 2, 19, &I_ButtonLeftSmall_3x5); canvas_draw_icon(canvas, 122, 19, &I_ButtonRightSmall_3x5); - for(uint8_t i = model->first_visible_byte; + for(uint16_t i = model->first_visible_byte; i < model->first_visible_byte + MIN(model->bytes_count, max_drawable_bytes); i++) { uint8_t byte_position = i - model->first_visible_byte; @@ -305,7 +305,7 @@ static void byte_input_draw_input_selected(Canvas* canvas, ByteInputModel* model * @param value char value * @param high_nibble set high nibble */ -static void byte_input_set_nibble(uint8_t* data, uint8_t position, char value, bool high_nibble) { +static void byte_input_set_nibble(uint8_t* data, uint16_t position, char value, bool high_nibble) { switch(value) { case '0': case '1': @@ -750,7 +750,7 @@ void byte_input_set_result_callback( ByteChangedCallback changed_callback, void* callback_context, uint8_t* bytes, - uint8_t bytes_count) { + uint16_t bytes_count) { with_view_model( byte_input->view, ByteInputModel * model, diff --git a/applications/services/gui/modules/byte_input.h b/applications/services/gui/modules/byte_input.h index 42c4b5d65..b8a4d4455 100644 --- a/applications/services/gui/modules/byte_input.h +++ b/applications/services/gui/modules/byte_input.h @@ -55,7 +55,7 @@ void byte_input_set_result_callback( ByteChangedCallback changed_callback, void* callback_context, uint8_t* bytes, - uint8_t bytes_count); + uint16_t bytes_count); /** Set byte input header text * diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 84d0beda8..4ea51493c 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -626,7 +626,7 @@ Function,+,byte_input_alloc,ByteInput*, Function,+,byte_input_free,void,ByteInput* Function,+,byte_input_get_view,View*,ByteInput* Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" -Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" +Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint16_t" Function,-,bzero,void,"void*, size_t" Function,-,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* diff --git a/lib/lfrfid/lfrfid_dict_file.c b/lib/lfrfid/lfrfid_dict_file.c index 7ae84f8b6..f8c3d5ef7 100644 --- a/lib/lfrfid/lfrfid_dict_file.c +++ b/lib/lfrfid/lfrfid_dict_file.c @@ -5,6 +5,42 @@ #define LFRFID_DICT_FILETYPE "Flipper RFID key" +bool lfrfid_dict_file_save_hitag1_data(FlipperFormat* file, uint8_t* data) { + FuriString* string = furi_string_alloc(); + bool result = false; + uint8_t pageSize = 4; + + do { + //write shortened data (tag ID) + if(!flipper_format_write_hex(file, "Data", data, pageSize)) break; + + if(!flipper_format_write_comment_cstr(file, "Hitag1 specific data")) break; + + //write pages + for(uint8_t p = 0; p < 64; p++) { + furi_string_printf(string, "Page %2u", p); + if(data[64 * pageSize + p]) { + //write page data + if(!flipper_format_write_hex( + file, furi_string_get_cstr(string), data + p * pageSize, pageSize)) + break; + } else { + //write ?? ?? ?? ?? + if(!flipper_format_write_string_cstr( + file, furi_string_get_cstr(string), "?? ?? ?? ??")) + break; + } + if(p == 64 - 1) { + result = true; + } + } + } while(false); + + furi_string_free(string); + + return result; +} + bool lfrfid_dict_file_save(ProtocolDict* dict, ProtocolId protocol, const char* filename) { furi_check(protocol != PROTOCOL_NO); Storage* storage = furi_record_open(RECORD_STORAGE); @@ -26,8 +62,12 @@ bool lfrfid_dict_file_save(ProtocolDict* dict, ProtocolId protocol, const char* // TODO: write comment about protocol sizes into file protocol_dict_get_data(dict, protocol, data, data_size); + if(protocol == LFRFIDProtocolHitag1) { + if(!lfrfid_dict_file_save_hitag1_data(file, data)) break; + } else { + if(!flipper_format_write_hex(file, "Data", data, data_size)) break; + } - if(!flipper_format_write_hex(file, "Data", data, data_size)) break; result = true; } while(false); @@ -138,6 +178,41 @@ static ProtocolId lfrfid_dict_protocol_fallback( return result; } +bool lfrfid_dict_file_load_hitag1_data(FlipperFormat* file, uint8_t* data) { + FuriString* string = furi_string_alloc(); + bool result = false; + uint8_t tagID[4]; + uint8_t pageSize = 4; + + do { + //read shortened data (tag ID) + if(!flipper_format_read_hex(file, "Data", tagID, 4)) break; + + //read pages + for(uint8_t p = 0; p < 64; p++) { + furi_string_printf(string, "Page %2u", p); + if(flipper_format_read_hex( + file, furi_string_get_cstr(string), data + p * pageSize, pageSize)) { + data[64 * pageSize + p] = 1; + } else { + data[64 * pageSize + p] = 0; + } + } + + //check data consistency + if(memcmp(tagID, data, pageSize) != 0) break; + + //check if tag ID & config page are succesfully read + if(data[64 * pageSize + 0] && data[64 * pageSize + 1]) { + result = true; + } + } while(false); + + furi_string_free(string); + + return result; +} + ProtocolId lfrfid_dict_file_load(ProtocolDict* dict, const char* filename) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* file = flipper_format_file_alloc(storage); @@ -163,6 +238,11 @@ ProtocolId lfrfid_dict_file_load(ProtocolDict* dict, const char* filename) { if(protocol == PROTOCOL_NO) { protocol = lfrfid_dict_protocol_fallback(dict, furi_string_get_cstr(str_result), file); if(protocol == PROTOCOL_NO) break; + } else if(protocol == LFRFIDProtocolHitag1) { + // Hitag1 data + size_t data_size = protocol_dict_get_data_size(dict, protocol); + if(!lfrfid_dict_file_load_hitag1_data(file, data)) break; + protocol_dict_set_data(dict, protocol, data, data_size); } else { // data size_t data_size = protocol_dict_get_data_size(dict, protocol); diff --git a/lib/lfrfid/lfrfid_hitag_worker.c b/lib/lfrfid/lfrfid_hitag_worker.c new file mode 100644 index 000000000..02cd14ca0 --- /dev/null +++ b/lib/lfrfid/lfrfid_hitag_worker.c @@ -0,0 +1,2370 @@ +#include "lfrfid_hitag_worker.h" + +#define DMA_BUFFER_SIZE \ + 4000 //I need to keep this big enough? to have enough time inbetween HT & TC events perhaps? +#define HITAG_CMD_BUFFER_SIZE 100 //this was originally 1000, but can be lowered + +#define READ_BUFFER_SIZE 4000 +#define READ_BUFFER_COUNT 4 + +#define EMULATE_BUFFER_SIZE 50 +#define EMULATE_BUFFER_COUNT 50 + +//bitlength/ID for read cmd structure +#define HITAG_STOP 102 +#define HITAG_ON 101 +#define HITAG_OFF 100 +#define HITAG_SET 5 +#define HITAG_SELECT 5 +#define HITAG_CMD 4 +#define HITAG_BYTE 8 +#define HITAG_CRC 8 + +//carrier periods for read cmd structure +#define HITAG_LOW 7 +#define HITAG_STOP_HIGH 40 +#define HITAG_1_HIGH 21 +#define HITAG_0_HIGH 15 + +//bitlength for emulate reply cmd structure +#define HITAG_PAGE 32 +#define HITAG_STARTBIT 1 +#define HITAG_STARTBIT3 3 +#define HITAG_STARTBIT6 6 +#define HITAG_ACK 3 +#define HITAG_ACK_ADV 8 + +#define HITAG_BASEPERIOD 8 + +//carrier periods for read reply decoding +#define HITAG_DURATION_S 256 +#define HITAG_DURATION_M 384 +#define HITAG_DURATION_L 512 +#define HITAG_DURATION_ERROR_MARGIN 0.15 + +#define HITAG_BITPERIODS_AC 64 +#define HITAG_BITPERIODS_MC 32 + +//MISC +#define READ 1 +#define WRITE 2 + +#define CRC_PRESET 0xFF +#define CRC_POLYNOM 0x1D + +#define TAG "LFRFIDHitagWorker" + +// TIMER definitions +#define CARRIER_OUT_TIMER TIM1 +//#define CARRIER_OUT_TIMER_IRQ FuriHalInterruptIdTIM1 //TODO am i using this? +#define CARRIER_OUT_TIMER_CHANNEL LL_TIM_CHANNEL_CH1 // or LL_TIM_CHANNEL_CH1N + +#define CARRIER_IN_TIMER TIM2 +#define CARRIER_IN_TIMER_IND_CH LL_TIM_CHANNEL_CH2 //no longer used via ETR setup +#define CARRIER_IN_TIMER_DIR_CH LL_TIM_CHANNEL_CH1 //no longer used via ETR setup + +#define CARRIER_IN_REFERENCE_TIMER TIM1 + +#define PULL_OUT_TIMER TIM2 +#define PULL_OUT_TIMER_CHANNEL LL_TIM_CHANNEL_CH3 + +// DMA Channels definition +#define PULL_OUT_DMA DMA2 +#define PULL_OUT_DMA_CH1 LL_DMA_CHANNEL_1 +#define PULL_OUT_DMA_CH2 LL_DMA_CHANNEL_2 +#define PULL_OUT_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1 +#define PULL_OUT_DMA_CH1_DEF PULL_OUT_DMA, PULL_OUT_DMA_CH1 +#define PULL_OUT_DMA_CH2_DEF PULL_OUT_DMA, PULL_OUT_DMA_CH2 + +typedef enum { + HitagProtocolAntiCollision, + HitagProtocolManchesterCoding, +} LfRfidHitagProtocol; + +typedef enum { + LFRFIDHitagWorkerSignalHalfTransfer, + LFRFIDHitagWorkerSignalTransferComplete, + LFRFIDHitagWorkerSignalStop, +} LFRFIDHitagWorkerSignal; + +typedef enum { + HitagStateIdle, + HitagStateSelected, + HitagStateQuiet, +} LfRfidHitagState; + +typedef enum { + HitagModeBasic, + HitagModeAdvanced, +} LfRfidHitagMode; + +typedef struct { + uint32_t timer_buffer_arr[DMA_BUFFER_SIZE]; + uint32_t timer_buffer_ccr[DMA_BUFFER_SIZE]; +} TimerDMAData; + +typedef struct { + uint32_t CMDvalue[HITAG_CMD_BUFFER_SIZE]; //this can likely be a uint16_t instead of uint32_t + uint8_t CMDtype[HITAG_CMD_BUFFER_SIZE]; + uint16_t CMDlength; + uint16_t CMDposition; + uint16_t CMDsubposition; + bool CMDloop; + bool CMDactive; + uint32_t CMDcount; +} LfRfidHitagCMDData; + +typedef struct { + uint32_t replyvalue[HITAG_CMD_BUFFER_SIZE]; //this can likely be a uint8_t instead of uint32_t + uint8_t replytype[HITAG_CMD_BUFFER_SIZE]; + uint16_t replylength; +} LfRfidHitagReplyData; + +typedef struct { + BufferStream* stream; + VarintPair* pair; + uint32_t capCounter; + uint32_t prevTIMval; +} LfRfidHitagCaptureData; + +typedef struct { + uint8_t pageData[HITAG_PAGES * HITAG_PAGEBYTES]; + uint8_t pageKnown[HITAG_PAGES]; + uint8_t tagData[HITAG_PAGES * HITAG_PAGEBYTES + HITAG_PAGES]; + uint8_t pageRW[HITAG_PAGES]; + uint8_t pagePublic[HITAG_PAGES]; +} LFRFIDHitag; + +// main worker +struct LFRFIDHitagWorker { + FuriThread* thread; + FuriEventFlag* events; + uint8_t DMAeventCount; + + uint8_t carrierPrescaler; + + LFRFIDHitag* tag; + + LFRFIDHitagStatus workerstatus; + + ProtocolDict* dict; +}; + +//------------------------------------------------------------------ forward function definitions ------------------------------------------------------------------ + +static int32_t lfrfid_hitag_worker_emulate_thread(void* thread_context); +static int32_t lfrfid_hitag_worker_read_thread(void* thread_context); + +static void lfrfid_hitag_worker_carrier_in_IC_mode(void* capture_context); + +//------------------------------------------------------------------ shared functions ------------------------------------------------------------------ + +LFRFIDHitagWorker* lfrfid_hitag_worker_alloc(ProtocolDict* dict) { + furi_assert(dict); + + LFRFIDHitagWorker* worker = malloc(sizeof(LFRFIDHitagWorker)); + worker->dict = dict; + + worker->thread = furi_thread_alloc_ex("LFRFIDHitagWorker", 2048, NULL, worker); + + worker->events = furi_event_flag_alloc(NULL); + worker->DMAeventCount = 0; + + worker->carrierPrescaler = 2; + + worker->workerstatus = LFRFIDHitagStatusScanning; + + return worker; +} + +void lfrfid_hitag_worker_free(LFRFIDHitagWorker* worker) { + furi_thread_free(worker->thread); + furi_event_flag_free(worker->events); + free(worker); +} + +LFRFIDHitagStatus lfrfid_hitag_worker_get_status(LFRFIDHitagWorker* worker) { + return worker->workerstatus; +} + +void lfrfid_hitag_worker_start(LFRFIDHitagWorker* worker, LFRFIDHitagWorkerSetting setting) { + if(furi_thread_get_state(worker->thread) == FuriThreadStateStopped) { + switch(setting) { + case(LFRFIDHitagWorkerSettingRead): + DOLPHIN_DEED(DolphinDeedRfidRead); + furi_thread_set_callback(worker->thread, lfrfid_hitag_worker_read_thread); + break; + case(LFRFIDHitagWorkerSettingEmulate): + DOLPHIN_DEED(DolphinDeedRfidEmulate); + furi_thread_set_callback(worker->thread, lfrfid_hitag_worker_emulate_thread); + break; + default: //safety meassure, not to start a thread without a callback function + furi_thread_set_callback(worker->thread, lfrfid_hitag_worker_read_thread); + break; + } + furi_thread_start(worker->thread); + } +} + +void lfrfid_hitag_worker_stop(LFRFIDHitagWorker* worker) { + furi_event_flag_set(worker->events, 1 << LFRFIDHitagWorkerSignalStop); + furi_thread_join(worker->thread); +} + +//------------------------------------------------------------------ READ TAG: capture input interrupt functions ------------------------------------------------------------------ + +static void lfrfid_hitag_worker_capture_in_cc_isr(bool level, uint32_t duration, void* context) { + LfRfidHitagCaptureData* capData = context; + + //check if there is a new pair available: pulse + period + bool need_to_send = varint_pair_pack(capData->pair, level, duration); + + //if so put it on buffer stream so it can be processed outside this interrupt routine + if(need_to_send) { + buffer_stream_send_from_isr( + capData->stream, + varint_pair_get_data(capData->pair), + varint_pair_get_size(capData->pair)); + varint_pair_reset(capData->pair); + } +} + +//------------------------------------------------------------------ READ TAG: carrier out TIMER, DMA & interrupts functions ------------------------------------------------------------------ + +static void lfrfid_hitag_worker_carrier_out_dma_isr(void* dma_context) { + LFRFIDHitagWorker* worker = (LFRFIDHitagWorker*)dma_context; + worker->DMAeventCount++; + + if(LL_DMA_IsActiveFlag_HT1(DMA1)) { + LL_DMA_ClearFlag_HT1(DMA1); + //halfway through DMA buffer --> first half can be repopulated + //just signal that it can be repopulated, not execute the repopulating itself, since this is an interrupt, holding up all other stuff + furi_event_flag_set(worker->events, 1 << LFRFIDHitagWorkerSignalHalfTransfer); + } + + if(LL_DMA_IsActiveFlag_TC1(DMA1)) { + LL_DMA_ClearFlag_TC1(DMA1); + //fully through DMA buffer --> second half can be repopulated + //just signal that it can be repopulated, not execute the repopulating itself, since this is an interrupt, holding up all other stuff + furi_event_flag_set(worker->events, 1 << LFRFIDHitagWorkerSignalTransferComplete); + } +} + +void lfrfid_hitag_worker_carrier_out_start( + uint32_t* duration, + uint32_t* pulse, + size_t length, + void* context) { + // configure timer + FURI_CRITICAL_ENTER(); + LL_TIM_DeInit(CARRIER_OUT_TIMER); + FURI_CRITICAL_EXIT(); + + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + TIM_InitStruct.Prescaler = SystemCoreClock / (125000 * HITAG_BASEPERIOD) - + 1; // sets the basis TIMER frequency to 8*freq (1MHz) for the timer + TIM_InitStruct.Autoreload = + HITAG_BASEPERIOD - + 1; //initial PWM period =125kHz (ARR will be handled via DMA further on) + LL_TIM_Init(CARRIER_OUT_TIMER, &TIM_InitStruct); + LL_TIM_DisableARRPreload(CARRIER_OUT_TIMER); + + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; + TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; + TIM_OC_InitStruct.OCNState = LL_TIM_OCSTATE_ENABLE; + TIM_OC_InitStruct.CompareValue = + HITAG_BASEPERIOD / + 2; //initial pulse duration of half period ((CCR will be handled via DMA further on) + LL_TIM_OC_Init(CARRIER_OUT_TIMER, CARRIER_OUT_TIMER_CHANNEL, &TIM_OC_InitStruct); + + LL_TIM_OC_SetPolarity(CARRIER_OUT_TIMER, CARRIER_OUT_TIMER_CHANNEL, LL_TIM_OCPOLARITY_HIGH); + LL_TIM_EnableDMAReq_UPDATE(CARRIER_OUT_TIMER); + + // configure DMA "mem -> ARR" channel + LL_DMA_InitTypeDef dma_config = {0}; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (CARRIER_OUT_TIMER->ARR); + dma_config.MemoryOrM2MDstAddress = (uint32_t)duration; + 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_WORD; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + dma_config.NbData = length; + dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP; + dma_config.Priority = LL_DMA_MODE_NORMAL; + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); + + // configure DMA "mem -> CCR1" channel + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (CARRIER_OUT_TIMER->CCR1); + dma_config.MemoryOrM2MDstAddress = (uint32_t)pulse; + 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_WORD; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + dma_config.NbData = length; + dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP; + dma_config.Priority = LL_DMA_MODE_NORMAL; + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + + // attach interrupt to one of DMA channels + furi_hal_interrupt_set_isr( + FuriHalInterruptIdDma1Ch1, lfrfid_hitag_worker_carrier_out_dma_isr, context); + LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_1); + + // start + LL_TIM_EnableAllOutputs(CARRIER_OUT_TIMER); + + LL_TIM_SetCounter(CARRIER_OUT_TIMER, 0); + LL_TIM_EnableCounter(CARRIER_OUT_TIMER); +} + +void lfrfid_hitag_worker_carrier_out_stop() { + LL_TIM_DisableCounter(CARRIER_OUT_TIMER); + LL_TIM_DisableAllOutputs(CARRIER_OUT_TIMER); + + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); + LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_1); + + FURI_CRITICAL_ENTER(); + + LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_2); + LL_TIM_DeInit(CARRIER_OUT_TIMER); + + FURI_CRITICAL_EXIT(); +} + +//------------------------------------------------------------------ EMULATE TAG: pull out TIMER, DMA & interrupts functions ------------------------------------------------------------------ + +static void lfrfid_hitag_worker_pull_out_dma_stop(void* capture_context) { + //reconfigure pull_out pin to fixed low state + //via forced OC INACTIVE MODE + LL_TIM_OC_SetMode(PULL_OUT_TIMER, PULL_OUT_TIMER_CHANNEL, LL_TIM_OCMODE_FORCED_INACTIVE); + + //disable DMA interrupts + //furi_hal_interrupt_set_isr(PULL_OUT_DMA_CH1_IRQ, NULL, NULL); + //LL_DMA_DisableIT_HT(PULL_OUT_DMA_CH1_DEF); + //LL_DMA_DisableIT_TC(PULL_OUT_DMA_CH1_DEF); + + //disable DMA channels & requests + LL_TIM_DisableDMAReq_UPDATE(PULL_OUT_TIMER); + LL_DMA_DisableChannel(PULL_OUT_DMA_CH1_DEF); //need to disable when using normal mode? + LL_DMA_DisableChannel(PULL_OUT_DMA_CH2_DEF); //need to disable when using normal mode? + //LL_TIM_DisableAllOutputs(PULL_OUT_TIMER); //no need to disable when reconfiguring pin? + + //for logging both ccr & arr times during emulate (iso only arr) + LL_TIM_DisableIT_CC3(PULL_OUT_TIMER); + + //switch carrier detection back to input capture for stable readings (required for command detection) + lfrfid_hitag_worker_carrier_in_IC_mode(capture_context); +} + +static void lfrfid_hitag_worker_pull_out_dma_isr(void* capture_context) { + /* currently no HT interrupt enabled, only TC + if(LL_DMA_IsActiveFlag_HT1(PULL_OUT_DMA)) { + LL_DMA_ClearFlag_HT1(PULL_OUT_DMA); + //lfrfid_hitag_worker_pull_out_dma_stop(); + } + */ + + if(LL_DMA_IsActiveFlag_TC1(PULL_OUT_DMA)) { + LL_DMA_ClearFlag_TC1(PULL_OUT_DMA); + lfrfid_hitag_worker_pull_out_dma_stop(capture_context); + } +} + +static void lfrfid_hitag_worker_pull_out_dma_start(size_t length) { + //array pointers remain the same, but length changes so + //reset DMA length (only possible when DMA channel is disabled + //this also resets the DMA counter (the DMA length is the 'remaining counter' + LL_DMA_SetDataLength(PULL_OUT_DMA_CH1_DEF, length); + LL_DMA_SetDataLength(PULL_OUT_DMA_CH2_DEF, length); + + //enable DMA + LL_DMA_EnableChannel(PULL_OUT_DMA_CH1_DEF); + LL_DMA_EnableChannel(PULL_OUT_DMA_CH2_DEF); + + //set OC to PWM1 mode + LL_TIM_OC_SetMode( + PULL_OUT_TIMER, + PULL_OUT_TIMER_CHANNEL, + LL_TIM_OCMODE_PWM1); //during emulate (dma controlled) mode + + //only enable DMA requests on timer update after enabling both channels (otherwise you risk that first DMA request only triggers one of the channels) + LL_TIM_EnableDMAReq_UPDATE(PULL_OUT_TIMER); + + //for logging both ccr & arr times during emulate (iso only arr) + LL_TIM_EnableIT_CC3(PULL_OUT_TIMER); +} + +static void lfrfid_hitag_worker_pull_out_dma_setup( + uint32_t* duration, + uint32_t* pulse, + size_t length, + void* capture_context) { + //DMA setup + LL_TIM_DisableDMAReq_UPDATE(PULL_OUT_TIMER); //start with DMA requests disabled + + // configure DMA "mem -> ARR" channel + LL_DMA_InitTypeDef dma_config = {0}; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (PULL_OUT_TIMER->ARR); + dma_config.MemoryOrM2MDstAddress = (uint32_t)duration; + dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + //dma_config.Mode = LL_DMA_MODE_CIRCULAR; //keep cycling through memory array + dma_config.Mode = LL_DMA_MODE_NORMAL; //cycle only once through memory array + 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 = length; + dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + dma_config.Priority = LL_DMA_MODE_NORMAL; + LL_DMA_Init(PULL_OUT_DMA_CH1_DEF, &dma_config); + + // configure DMA "mem -> CCR3" channel + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (PULL_OUT_TIMER->CCR3); + dma_config.MemoryOrM2MDstAddress = (uint32_t)pulse; + dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + //dma_config.Mode = LL_DMA_MODE_CIRCULAR; //keep cycling through memory array + dma_config.Mode = LL_DMA_MODE_NORMAL; //cycle only once through memory array + 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 = length; + dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + dma_config.Priority = LL_DMA_MODE_NORMAL; + LL_DMA_Init(PULL_OUT_DMA_CH2_DEF, &dma_config); + + // enable DMA interrupts + furi_hal_interrupt_set_isr( + PULL_OUT_DMA_CH1_IRQ, lfrfid_hitag_worker_pull_out_dma_isr, capture_context); + //LL_DMA_EnableIT_HT(PULL_OUT_DMA_CH1_DEF); //let's try normal DMA mode and only transfer complete interrupt + LL_DMA_EnableIT_TC(PULL_OUT_DMA_CH1_DEF); +} + +//------------------------------------------------------------------ EMULATE TAG: carrier input TIMER & interrupt functions (switching input capture & ETR)------------------------------------------------------------------ + +static void lfrfid_hitag_worker_carrier_in_isr(void* capture_context) { + if(LL_TIM_IsActiveFlag_CC1(CARRIER_IN_TIMER)) { + uint32_t newTIMval = LL_TIM_IC_GetCaptureCH1(CARRIER_IN_TIMER); + LL_TIM_ClearFlag_CC1(CARRIER_IN_TIMER); + + //ARR for TIM2 in input capture mode is UINT32_MAX so at freq of 1MHz this is 70+ minutes, instead of resetting counter, just store prev value and take difference of both + //this will increase measurement accuracy, since both values are then purely hardware controlled, no dependency of interrupt timing to (re)set any timer values + //LL_TIM_SetCounter(CARRIER_IN_TIMER, 0); + + LfRfidHitagCaptureData* capData = capture_context; + capData->capCounter++; + //pack varint (as part of a pair) + //cleaner would be to pack as a varint iso varint_pair, though i'm not sure if I can get the data & size of a packed varint + varint_pair_pack(capData->pair, true, newTIMval - capData->prevTIMval); + //but send anyhow, doesn't matter that it's not a pair + buffer_stream_send_from_isr( + capData->stream, + varint_pair_get_data(capData->pair), + varint_pair_get_size(capData->pair)); + varint_pair_reset(capData->pair); + + capData->prevTIMval = newTIMval; + } else if(LL_TIM_IsActiveFlag_UPDATE(CARRIER_IN_TIMER)) { + uint32_t newTIMval = LL_TIM_GetCounter(CARRIER_IN_REFERENCE_TIMER); + LL_TIM_ClearFlag_UPDATE(CARRIER_IN_TIMER); + + //ARR for ref timer in ETR mode is UINT32_MAX so at freq of 1MHz this is 70+ minutes, instead of resetting counter, just store prev value and take difference of both + //this will increase measurement accuracy, since both values are then purely hardware controlled, no dependency of interrupt timing to (re)set any timer values + LL_TIM_SetCounter(CARRIER_IN_REFERENCE_TIMER, 0); + + LfRfidHitagCaptureData* capData = capture_context; + capData->capCounter++; + //pack varint (as part of a pair) + //cleaner would be to pack as a varint iso varint_pair, though i'm not sure if I can get the data & size of a packed varint + varint_pair_pack(capData->pair, true, newTIMval); // - capData->prevTIMval); + //but send anyhow, doesn't matter that it's not a pair + buffer_stream_send_from_isr( + capData->stream, + varint_pair_get_data(capData->pair), + varint_pair_get_size(capData->pair)); + varint_pair_reset(capData->pair); + + //capData->prevTIMval = newTIMval; + } else if(LL_TIM_IsActiveFlag_CC3(CARRIER_IN_TIMER)) { + uint32_t newTIMval = LL_TIM_GetCounter(CARRIER_IN_REFERENCE_TIMER); + LL_TIM_ClearFlag_CC3(CARRIER_IN_TIMER); + + //don't reset timer for the ccr (HIGH) timing, this way the arr (duration) timing is still full duraton timing (iso only LOW timing) + //LL_TIM_SetCounter(CARRIER_IN_REFERENCE_TIMER, 0); + + LfRfidHitagCaptureData* capData = capture_context; + capData->capCounter++; + //pack varint (as part of a pair) + //cleaner would be to pack as a varint iso varint_pair, though i'm not sure if I can get the data & size of a packed varint + varint_pair_pack(capData->pair, true, newTIMval); // - capData->prevTIMval); + //but send anyhow, doesn't matter that it's not a pair + buffer_stream_send_from_isr( + capData->stream, + varint_pair_get_data(capData->pair), + varint_pair_get_size(capData->pair)); + varint_pair_reset(capData->pair); + + //capData->prevTIMval = newTIMval; + } +} + +static void lfrfid_hitag_worker_carrier_in_ETR_mode(void* capture_context, uint8_t ext_prescaler) { + //disable counters temporarily + LL_TIM_DisableCounter(CARRIER_IN_TIMER); + LL_TIM_DisableCounter(CARRIER_IN_REFERENCE_TIMER); + + //disable Input capture & related interrupt + LL_TIM_DisableIT_CC1(CARRIER_IN_TIMER); + LL_TIM_CC_DisableChannel(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH); + + //reset capdata prev value + LfRfidHitagCaptureData* capData = capture_context; + capData->prevTIMval = 0; + + //switch clocksource to ETR with external prescaling via ARR + LL_TIM_SetPrescaler(CARRIER_IN_TIMER, 1 - 1); //prescaler is only applied at next update event + LL_TIM_GenerateEvent_UPDATE( + CARRIER_IN_TIMER); //prescaler is only applied at next update event //TODO: how to prevent this from creating a capdata entry + LL_TIM_SetAutoReload(CARRIER_IN_TIMER, ext_prescaler - 1); + LL_TIM_SetClockSource(CARRIER_IN_TIMER, LL_TIM_CLOCKSOURCE_EXT_MODE2); + + //reconfigure carrier_in pin to TIM2 ETR + furi_hal_gpio_init_ex( + &gpio_rfid_carrier, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn2TIM2); + + //reset timer counter & capture context for period calculation + LL_TIM_SetCounter(CARRIER_IN_TIMER, 0); + LL_TIM_SetCounter(CARRIER_IN_REFERENCE_TIMER, 0); + + //enable interrupt via update + LL_TIM_EnableIT_UPDATE(CARRIER_IN_TIMER); + + //re-enable counters + LL_TIM_EnableCounter(CARRIER_IN_TIMER); + LL_TIM_EnableCounter(CARRIER_IN_REFERENCE_TIMER); +} + +static void lfrfid_hitag_worker_carrier_in_IC_mode(void* capture_context) { + //disable counters temporarily + LL_TIM_DisableCounter(CARRIER_IN_TIMER); + LL_TIM_DisableCounter(CARRIER_IN_REFERENCE_TIMER); + + //disable interrupt via update + LL_TIM_DisableIT_UPDATE(CARRIER_IN_TIMER); + + //reset capdata prev value + LfRfidHitagCaptureData* capData = capture_context; + capData->prevTIMval = 0; + + //switch clocksource to system/64, external prescaling is handled in IC prescaler + LL_TIM_SetClockSource(CARRIER_IN_TIMER, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_SetPrescaler(CARRIER_IN_TIMER, 64 - 1); //prescaler is only applied at next update event + LL_TIM_GenerateEvent_UPDATE( + CARRIER_IN_TIMER); //prescaler is only applied at next update event //TODO: how to prevent this from creating a capdata entry + LL_TIM_SetAutoReload(CARRIER_IN_TIMER, UINT32_MAX); + + //reconfigure carrier_in pin to TIM2 CH1 for input capture + furi_hal_gpio_init_ex( + &gpio_rfid_carrier, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); + + //reset timer counter & capture context for period calculation + LL_TIM_SetCounter(CARRIER_IN_TIMER, 0); + LL_TIM_SetCounter(CARRIER_IN_REFERENCE_TIMER, 0); + + //enable Input capture & related interrupt + LL_TIM_EnableIT_CC1(CARRIER_IN_TIMER); + LL_TIM_CC_EnableChannel(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH); + + //re-enable counters + LL_TIM_EnableCounter(CARRIER_IN_TIMER); + LL_TIM_EnableCounter(CARRIER_IN_REFERENCE_TIMER); +} + +static void lfrfid_hitag_worker_carrier_in_start( + void* capture_context, + uint8_t ext_prescaler, + uint32_t* duration, + uint32_t* pulse, + size_t length) { + FURI_CRITICAL_ENTER(); + LL_DMA_DeInit(PULL_OUT_DMA_CH1_DEF); //required? + LL_DMA_DeInit(PULL_OUT_DMA_CH2_DEF); //required? + LL_TIM_DeInit(CARRIER_IN_TIMER); + LL_TIM_DeInit(CARRIER_IN_REFERENCE_TIMER); + FURI_CRITICAL_EXIT(); + + //setup reference timer: simple setup with base freq of 1MHz and max autoreload + LL_TIM_InitTypeDef TIM_InitStruct_Ref = {0}; + TIM_InitStruct_Ref.Prescaler = + 64 - + 1; //system base freq is 64MHz, so this sets base freq for TIM to 1MHz (aka 1us period) + TIM_InitStruct_Ref.CounterMode = LL_TIM_COUNTERMODE_UP; + TIM_InitStruct_Ref.Autoreload = UINT32_MAX; + TIM_InitStruct_Ref.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; + LL_TIM_Init(CARRIER_IN_REFERENCE_TIMER, &TIM_InitStruct_Ref); + + //setup carrier in timer for input capture + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + TIM_InitStruct.Prescaler = + 64 - + 1; //system base freq is 64MHz, so this sets base freq for TIM to 1MHz (aka 1us period) + TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; + TIM_InitStruct.Autoreload = UINT32_MAX; + TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; + LL_TIM_Init(CARRIER_IN_TIMER, &TIM_InitStruct); + + LL_TIM_DisableARRPreload(CARRIER_IN_TIMER); + LL_TIM_SetClockSource( + CARRIER_IN_TIMER, + LL_TIM_CLOCKSOURCE_INTERNAL); //default is internal, so likely not required + LL_TIM_DisableDMAReq_TRIG(CARRIER_IN_TIMER); + LL_TIM_DisableIT_TRIG(CARRIER_IN_TIMER); + + //meanwhile already prepre the ETR + LL_TIM_ConfigETR( + CARRIER_IN_TIMER, + LL_TIM_ETR_POLARITY_INVERTED, + LL_TIM_ETR_PRESCALER_DIV1, + LL_TIM_ETR_FILTER_FDIV1); + + //INPUT CAPTURE SETUP + // Timer: channel 1 direct (from GPIO) + LL_TIM_IC_SetActiveInput( + CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH, LL_TIM_ACTIVEINPUT_DIRECTTI); + //prescaling direct channel seems to be working fine (and is necessary since otherwise sd write cannot keep up) + if(ext_prescaler == 4) { + LL_TIM_IC_SetPrescaler(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH, LL_TIM_ICPSC_DIV4); + } else if(ext_prescaler == 2) { + LL_TIM_IC_SetPrescaler(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH, LL_TIM_ICPSC_DIV2); + } else { + LL_TIM_IC_SetPrescaler(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH, LL_TIM_ICPSC_DIV1); + } + LL_TIM_IC_SetPolarity(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH, LL_TIM_IC_POLARITY_RISING); + LL_TIM_IC_SetFilter(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH, LL_TIM_IC_FILTER_FDIV1); + /* Timer: channel 2 indirect (from channel 1) + // only measure & write to file the period (not the on & off cycle), this reduced CPU load on SD write to (hopefully) keep up with period measurements without prescaler + LL_TIM_IC_SetActiveInput(CARRIER_IN_TIMER, CARRIER_IN_TIMER_IND_CH, LL_TIM_ACTIVEINPUT_INDIRECTTI); + //presaling indirect channel doesn't really work well yet :/ + LL_TIM_IC_SetPrescaler(CARRIER_IN_TIMER, CARRIER_IN_TIMER_IND_CH, LL_TIM_ICPSC_DIV2); + LL_TIM_IC_SetPolarity(CARRIER_IN_TIMER, CARRIER_IN_TIMER_IND_CH, LL_TIM_IC_POLARITY_FALLING); + LL_TIM_IC_SetFilter(CARRIER_IN_TIMER, CARRIER_IN_TIMER_IND_CH, LL_TIM_IC_FILTER_FDIV1); + */ + + //set interrupt callback for capturing period + LL_TIM_EnableIT_CC1(CARRIER_IN_TIMER); + //LL_TIM_EnableIT_CC2(CARRIER_IN_TIMER); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdTIM2, lfrfid_hitag_worker_carrier_in_isr, capture_context); + + //OUTPUT COMPARE SETUP + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; + //TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; //during emulate (dma controlled) mode + TIM_OC_InitStruct.OCMode = + LL_TIM_OCMODE_FORCED_INACTIVE; //during carrier in put output to forced low state + TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; + TIM_OC_InitStruct.CompareValue = + 0; //0% this should have almost similar effect as keeping output forced inactive (there's still some micropulse emited, going high at ARR and immediately down again at CCR value) + LL_TIM_OC_Init(PULL_OUT_TIMER, PULL_OUT_TIMER_CHANNEL, &TIM_OC_InitStruct); + LL_TIM_OC_SetPolarity( + PULL_OUT_TIMER, + PULL_OUT_TIMER_CHANNEL, + LL_TIM_OCPOLARITY_HIGH); //active high (gpio goes high when pulse is high) + + //INIT DMA (do not start it yet) + lfrfid_hitag_worker_pull_out_dma_setup(duration, pulse, length, capture_context); + + LL_TIM_CC_EnableChannel(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH); + //LL_TIM_CC_EnableChannel(CARRIER_IN_TIMER, CARRIER_IN_TIMER_IND_CH); + LL_TIM_CC_EnableChannel(PULL_OUT_TIMER, PULL_OUT_TIMER_CHANNEL); + LL_TIM_SetCounter(CARRIER_IN_TIMER, 0); + LL_TIM_SetCounter(CARRIER_IN_REFERENCE_TIMER, 0); + LL_TIM_EnableCounter(CARRIER_IN_TIMER); + LL_TIM_EnableCounter(CARRIER_IN_REFERENCE_TIMER); +} + +void lfrfid_hitag_worker_carrier_in_stop() { + furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); + + LL_TIM_DisableCounter(CARRIER_IN_TIMER); + LL_TIM_DisableCounter(CARRIER_IN_REFERENCE_TIMER); + LL_TIM_DisableAllOutputs(CARRIER_IN_TIMER); //used for pull pin OC + + FURI_CRITICAL_ENTER(); + LL_DMA_DeInit(PULL_OUT_DMA_CH1_DEF); + LL_DMA_DeInit(PULL_OUT_DMA_CH2_DEF); + LL_TIM_DeInit(CARRIER_IN_TIMER); + LL_TIM_DeInit(CARRIER_IN_REFERENCE_TIMER); + FURI_CRITICAL_EXIT(); +} + +//------------------------------------------------------------------ hitag data parsing functions ------------------------------------------------------------------ + +static void lfrfid_hitag_worker_yield_dma_buffer_BPLM( + LfRfidHitagCMDData* dataCMD, + LfRfidHitagCMDData* backupCMD, + TimerDMAData* dataDMA, + size_t start, + size_t len) { + //based on interal timer with freq of 1MHz (period of 1us) + //use active command + LfRfidHitagCMDData* data = dataCMD; + if(!data->CMDactive) { + data = backupCMD; + } + uint16_t lastarrposition = 0; + size_t toskip = 0; + bool itemProcessed = true; + size_t s = start; + //for ( ; data->CMDloop || data->CMDposition < data->CMDlength ; data->CMDposition++){ + while(1) { + //at the end of current command (real or backup), check if there's unprocessed CMD available, if not switch to backup + if(data->CMDposition == data->CMDlength) { + data->CMDcount++; + if(dataCMD->CMDposition < + dataCMD + ->CMDlength) { //currently backup & new unprocessed command available --> switch over to real CMD + data->CMDactive = false; + data = dataCMD; + data->CMDactive = true; + data->CMDcount = 0; + } else if(dataCMD + ->CMDloop) { //if looping (real or backup) --> reset real (current) CMD to startposition + data->CMDposition = 0; + data->CMDsubposition = 0; + } else if(dataCMD + ->CMDactive) { //real command and not looping --> switch to backup CMD and reset backup CMD to startposition + data->CMDactive = false; + data = backupCMD; + data->CMDactive = true; + data->CMDposition = 0; + data->CMDsubposition = 0; + data->CMDcount = 0; + } else { //backupCMD but set to not loop --> loop anyway: reset to startposition + data->CMDposition = 0; + data->CMDsubposition = 0; + } + } + //start processing the next CMD item + lastarrposition = s; + if(s == start) { + toskip = data->CMDsubposition; + } else { + toskip = 0; + data->CMDsubposition = 0; + } + switch(data->CMDtype[data->CMDposition]) { + case HITAG_STOP: //just 1 STOP cycle (HITAG_LOW lows + HITAG_STOP_HIGH highs) + if(s > start) { + dataDMA->timer_buffer_arr[s - 1] += HITAG_LOW * HITAG_BASEPERIOD; + } + for(size_t t = 0; t < HITAG_STOP_HIGH - toskip; t++) { + if(s == len + start) { + //arrays are filled, do not continue out + itemProcessed = false; + break; + } + dataDMA->timer_buffer_ccr[s] = HITAG_BASEPERIOD / 2; + dataDMA->timer_buffer_arr[s] = HITAG_BASEPERIOD - 1; + s++; + } + break; + case HITAG_ON: //x ON cycles --> update next X arr/ccr values + for(size_t x = 0; x < data->CMDvalue[data->CMDposition] - toskip; x++) { + if(s == len + start) { + //arrays are filled, do not continue out + itemProcessed = false; + break; + } + dataDMA->timer_buffer_ccr[s] = HITAG_BASEPERIOD / 2; + dataDMA->timer_buffer_arr[s] = HITAG_BASEPERIOD - 1; + s++; + } + break; + case HITAG_OFF: //x OFF cycles (update previous arr value) + if(s > start) { + dataDMA->timer_buffer_arr[s - 1] += + data->CMDvalue[data->CMDposition] * HITAG_BASEPERIOD; + } + break; + default: // <=HITAG_BYTE: //bits to be sent + //get each bit & update arr/ccr accordingly (7 lows + HITAG_0_HIGH/HITAG_1_HIGH highs) + for(size_t b = 8 - data->CMDtype[data->CMDposition]; b < 8; b++) { + if(s > start) { + dataDMA->timer_buffer_arr[s - 1] += HITAG_LOW * HITAG_BASEPERIOD; + } + if(0x80 & (data->CMDvalue[data->CMDposition] << b)) { + for(size_t t = 0; t < HITAG_1_HIGH; t++) { + if(s == len + start) { + //arrays are filled, do not continue out + itemProcessed = false; + break; + } + dataDMA->timer_buffer_ccr[s] = HITAG_BASEPERIOD / 2; + dataDMA->timer_buffer_arr[s] = HITAG_BASEPERIOD - 1; + s++; + if(toskip > 0) { + toskip--; + s--; + } + } + } else { + for(size_t t = 0; t < HITAG_0_HIGH; t++) { + if(s == len + start) { + //arrays are filled, do not continue out + itemProcessed = false; + break; + } + dataDMA->timer_buffer_ccr[s] = HITAG_BASEPERIOD / 2; + dataDMA->timer_buffer_arr[s] = HITAG_BASEPERIOD - 1; + s++; + if(toskip > 0) { + toskip--; + s--; + } + } + } + if(!itemProcessed) { + break; + } + } + break; + } + if(!itemProcessed) { + break; + } + data->CMDposition++; + } + + /*if buffer is not full, but command is fully processed: fill remainder of buffer with normal on cycles + if (stimer_buffer_ccr[s]=HITAG_BASEPERIOD/4; //25%duty cycle + dataDMA->timer_buffer_arr[s]=1*HITAG_BASEPERIOD-1; + } else { + dataDMA->timer_buffer_ccr[s]=HITAG_BASEPERIOD/2; + dataDMA->timer_buffer_arr[s]=HITAG_BASEPERIOD-1; + } + } + //other stuff to do? + } + */ + + //if buffer was full, but command not yet fully processed: set starting position for next time: + if(!itemProcessed) { + data->CMDsubposition += (len + start) - lastarrposition; + } else { + data->CMDsubposition = 0; + } +} + +static uint16_t lfrfid_hitag_worker_yield_dma_buffer_MC( + LfRfidHitagReplyData* dataReply, + TimerDMAData* dataDMA, + uint8_t carrierPrescaler) { + //based on external carrier with freq of 125kHz (period of 8us) + + uint16_t dma_len = 0; + + bool bit = false; + bool prevBit = true; //must be true to initialze MC yield for first bit + + //process reply bits and add HIGH-LOW for 1 or LOW-HIGH for 0 + for(uint8_t i = 0; i < dataReply->replylength; i++) { + for(int8_t b = dataReply->replytype[i] - 1; b >= 0; b--) { + bit = (dataReply->replyvalue[i] >> b & 0b00000001); + if(bit) { //HIGH-LOW + if(prevBit) { //if first bit (initial value of prevBit=true) or if prev bit was 1: start of new default cycle + dataDMA->timer_buffer_arr[dma_len] = 32 - 1; + dataDMA->timer_buffer_ccr[dma_len] = 16; + dma_len++; + } else { //previous bit was 0, so extend the ccr and arr with 16 + dataDMA->timer_buffer_arr[dma_len - 1] += 16; + dataDMA->timer_buffer_ccr[dma_len - 1] += 16; + } + } else { //LOW-HIGH + if(prevBit) { //previous bit was 1 so extend the LOW (arr only) of prev cycle with 16 and add new default cycle + dataDMA->timer_buffer_arr[dma_len - 1] += 16; + + dataDMA->timer_buffer_arr[dma_len] = 32 - 1; + dataDMA->timer_buffer_ccr[dma_len] = 16; + dma_len++; + } else { //previous bit was 0 so just add new default cycle + dataDMA->timer_buffer_arr[dma_len] = 32 - 1; + dataDMA->timer_buffer_ccr[dma_len] = 16; + dma_len++; + } + } + prevBit = bit; + } + } + + //add extra period, to reset timer back to normal carrier_in detection after reply has finished + //also required to make sure the last real dma values are fully executed before dma signals transfer complete, which resets worker into command detection + dataDMA->timer_buffer_arr[dma_len] = carrierPrescaler - 1; + dataDMA->timer_buffer_ccr[dma_len] = 0; + dma_len++; + + return dma_len; +} + +static uint16_t lfrfid_hitag_worker_yield_dma_buffer_AC( + LfRfidHitagReplyData* dataReply, + TimerDMAData* dataDMA, + uint8_t carrierPrescaler) { + //based on external carrier with freq of 125kHz (period of 8us) + + uint16_t dma_len = 0; + + //process reply bits and add 2 short pulses for 1 or 1 long pulse for 0 + for(uint8_t i = 0; i < dataReply->replylength; i++) { + for(int8_t b = dataReply->replytype[i] - 1; b >= 0; b--) { + if(dataReply->replyvalue[i] >> b & 0b00000001) { + dataDMA->timer_buffer_arr[dma_len] = 32 - 1; + dataDMA->timer_buffer_ccr[dma_len] = 16; //16 + dma_len++; + dataDMA->timer_buffer_arr[dma_len] = 32 - 1; + dataDMA->timer_buffer_ccr[dma_len] = 16; //16 + dma_len++; + } else { + dataDMA->timer_buffer_arr[dma_len] = 64 - 1; + dataDMA->timer_buffer_ccr[dma_len] = 32; //32 + dma_len++; + } + } + } + + //add extra period, to reset timer back to normal carrier_in detection after reply has finished + //also required to make sure the last real dma values are fully executed before dma signals transfer complete, which resets worker into command detection + dataDMA->timer_buffer_arr[dma_len] = carrierPrescaler - 1; + dataDMA->timer_buffer_ccr[dma_len] = 0; + dma_len++; + + return dma_len; +} + +static void lfrfid_hitag_worker_decoder_feed( + uint32_t pulse, + uint32_t duration, + uint32_t* memBlock, + uint8_t* bits, + bool* half, + bool* finished, + LfRfidHitagProtocol protocol, + uint8_t startBits) { + uint32_t bit = 1 << (32 - 1); + if(*bits == 0) { + *half = false; + } + if(protocol == HitagProtocolManchesterCoding) { + if(HITAG_DURATION_S * (1 - HITAG_DURATION_ERROR_MARGIN) <= duration && + duration <= HITAG_DURATION_S * (1 + HITAG_DURATION_ERROR_MARGIN)) { + //short period (HL) detected: same bit as previous + if(*bits < startBits) { + //don't store startbits in memory, just increase bitcounter + (*bits)++; + } else if(*bits == startBits) { + //same as previous bit (last startbit it 1, so add 1 to the memBlock) + memBlock[(*bits - startBits) / 32] |= (bit >> (*bits - startBits) % 32); //add 1 + (*bits)++; + } else { + //add same as previous bit + if(memBlock[(*bits - startBits - 1) / 32] & + (bit >> (*bits - startBits - 1) % 32)) { + memBlock[(*bits - startBits) / 32] |= + (bit >> (*bits - startBits) % 32); //add 1 + (*bits)++; + } else { + (*bits)++; //no mem action for 0, just increase bitcounter + } + } + } else if( + HITAG_DURATION_M * (1 - HITAG_DURATION_ERROR_MARGIN) <= duration && + duration <= HITAG_DURATION_M * (1 + HITAG_DURATION_ERROR_MARGIN)) { + //medium period (H|HL or HL|L) detected: half 0 + full 1 or full 1 + half 0 detected + if(*bits < startBits) { + //assuming all startbits are 1's, this can only happen on last startbit followed by 0 + //don't store startbits, no mem action for 0, just increase bitcounter twice + (*bits)++; + (*bits)++; + *half = true; + } else if(*bits == startBits) { + //assuming all startbits were 1's, this is a data 10 detection + memBlock[(*bits - startBits) / 32] |= (bit >> (*bits - startBits) % 32); //add 1 + (*bits)++; + (*bits)++; //no mem action for 0, just increase bitcounter + *half = true; + } else { + //if we're in half of a bit, add 1, else add 10 and set half flag + if(*half) { + memBlock[(*bits - startBits) / 32] |= + (bit >> (*bits - startBits) % 32); //add 1 + (*bits)++; + *half = false; + } else { + memBlock[(*bits - startBits) / 32] |= + (bit >> (*bits - startBits) % 32); //add 1 + (*bits)++; + (*bits)++; //no mem action for 0, just increase bitcounter + *half = true; + } + } + } else if( + HITAG_DURATION_L * (1 - HITAG_DURATION_ERROR_MARGIN) <= duration && + duration <= HITAG_DURATION_L * (1 + HITAG_DURATION_ERROR_MARGIN)) { + //long period (H|HL|L) detected: half 0 + full 1 + half 0 detected + if(*bits <= startBits) { + //TODO: throw error since this can't happen till after first real bit, since all startbits should be 1's + } else { + //TODO check that prev bit is indeed 0 + //add 10 to the memBlock + memBlock[(*bits - startBits) / 32] |= (bit >> (*bits - startBits) % 32); //add 1 + (*bits)++; + (*bits)++; //no mem action for 0, just increase bitcounter + } + } else if(HITAG_DURATION_L * (1 + HITAG_DURATION_ERROR_MARGIN) < duration) { + //end of modulation by tag, check if this pulse duration includes a bit + if(*bits > 0) { + if(*half && pulse < HITAG_DURATION_S) { + //second half of 0 bit, no action + } else if(*half && pulse < HITAG_DURATION_M) { + //second half or 0 bit + another 1 bit + memBlock[(*bits - startBits) / 32] |= + (bit >> (*bits - startBits) % 32); //add 1 + (*bits)++; + } else if(!(*half) && pulse < HITAG_DURATION_S) { + //another 1 bit + memBlock[(*bits - startBits) / 32] |= + (bit >> (*bits - startBits) % 32); //add 1 + (*bits)++; + } else { + //invalid pulse duration --> reset? + } + *finished = true; + } + } else { + //invalid pulse duration: reset tempData if necessary + if(*bits > 0) { + memset(memBlock, 0, HITAG_BLOCKPAGES * sizeof(memBlock[0])); + *bits = 0; + *finished = false; + } + } + } else if(protocol == HitagProtocolAntiCollision) { + if(HITAG_DURATION_S * (1 - HITAG_DURATION_ERROR_MARGIN) <= duration && + duration <= HITAG_DURATION_S * (1 + HITAG_DURATION_ERROR_MARGIN)) { + //short period (HL): first or second half of a 1 detected + //if first short: add 1 to memBlock and increase half counter + if(*half == false) { + if(*bits >= startBits) { + memBlock[(*bits - startBits) / 32] |= (bit >> (*bits - startBits) % 32); + } + (*bits)++; + *half = true; + } else { + //if second small: reset half counter + *half = false; + } + } else if( + HITAG_DURATION_L * (1 - HITAG_DURATION_ERROR_MARGIN) <= duration && + duration <= HITAG_DURATION_L * (1 + HITAG_DURATION_ERROR_MARGIN)) { + //long period (HHLL): 0 detected + //should only happen after full detection of 1 or after startBits + if(*bits < startBits || *half) { + //reset + memset(memBlock, 0, HITAG_BLOCKPAGES * sizeof(memBlock[0])); + *bits = 0; + *finished = false; + } else { + (*bits)++; //add 0 to memBlock + } + } else if(HITAG_DURATION_L * (1 + HITAG_DURATION_ERROR_MARGIN) < duration) { + //end of modulation by tag, check if this pulse duration includes a bit + if(*bits > 0) { + if(!(*half) && pulse > HITAG_DURATION_S) { + (*bits)++; //add 0 to memBlock + } else { + //invalid pulse duration: reset? + } + *finished = true; + } + } else { + //invalid pulse duration: reset tempData if necessary + if(*bits > 0) { + memset(memBlock, 0, HITAG_BLOCKPAGES * sizeof(memBlock[0])); + *bits = 0; + *finished = false; + } + } + } +} + +static void lfrfid_hitag_worker_calc_crc(uint8_t* crc, uint8_t data, uint8_t bitcount) { + *crc ^= data; // crc = crc (exor) data + do { + if(*crc & 0x80) // if (MSB-CRC == 1) + { + *crc <<= 1; // CRC = CRC bit-shift left + *crc ^= CRC_POLYNOM; // CRC = CRC (exor) CRC_POLYNOM + } else { + *crc <<= 1; // CRC = CRC bit-shift left + } + } while(--bitcount); +} + +static bool lfrfid_hitag_worker_validate_command(uint32_t* bits, uint8_t len) { + if(len == 5 && (bits[0] >> (32 - 5)) == 0b11001) { + //SET_CCNEW + return true; + } else if(len == 5 && (bits[0] >> (32 - 5)) == 0b00110) { + //SET_CC + return true; + } else if(len == 45 && (bits[0] >> (32 - 5)) == 0b00000) { + //SELECT + + //calculate CRC for first 37 bits (4bytes + 5 bits) + uint8_t byte1 = bits[0] >> 24 & 0xff; + uint8_t byte2 = bits[0] >> 16 & 0xff; + uint8_t byte3 = bits[0] >> 8 & 0xff; + uint8_t byte4 = bits[0] & 0xff; + uint8_t byte5 = bits[1] >> 24 & 0b11111000; + uint8_t crcRead = bits[1] >> 19 & 0xff; + + uint8_t crc = CRC_PRESET; + lfrfid_hitag_worker_calc_crc(&crc, byte1, 8); + lfrfid_hitag_worker_calc_crc(&crc, byte2, 8); + lfrfid_hitag_worker_calc_crc(&crc, byte3, 8); + lfrfid_hitag_worker_calc_crc(&crc, byte4, 8); + lfrfid_hitag_worker_calc_crc(&crc, byte5, 5); + + //and compare result with received crc + return (crcRead == crc); + } + //else if (len == 20 && (bits[0]>>31 || (bits[0]>>(32-4)) == 0b0111)){ + else if(len == 20 && bits[0] > 0x70000000) { + //R/W page/block (cmd starting with 1) or HALT (0111) --> so bit0 should be >= 0b01110000 00000000 00000000 00000000 (0x70000000) + //calculate CRC for first 12 bits (1byte + 4 bits) + uint8_t byte1 = bits[0] >> 24 & 0xff; + uint8_t byte2 = bits[0] >> 16 & 0b11110000; + uint8_t crcRead = bits[0] >> 12 & 0xff; + + uint8_t crc = CRC_PRESET; + lfrfid_hitag_worker_calc_crc(&crc, byte1, 8); + lfrfid_hitag_worker_calc_crc(&crc, byte2, 4); + + //and compare result with received crc + return (crcRead == crc); + } + return false; +} + +static bool lfrfid_hitag_worker_validate_bits(uint32_t* bits, uint8_t bitcounter) { + if(47 >= bitcounter && bitcounter >= 45) { + for(uint8_t i = 0; i < bitcounter - 44; i++) { + if(i > 0) { + bits[0] = bits[0] << 1 | bits[1] >> (32 - 1); + bits[1] = bits[1] << 1; + } + if(lfrfid_hitag_worker_validate_command(bits, 45)) return true; + } + } else if(22 >= bitcounter && bitcounter >= 20) { + for(uint8_t i = 0; i < bitcounter - 19; i++) { + if(i > 0) { + bits[0] = bits[0] << 1; // | bits[1]>>(32-1); + //bits[1] = bits[1]<<1; + } + if(lfrfid_hitag_worker_validate_command(bits, 20)) return true; + } + } else if(7 >= bitcounter && bitcounter >= 5) { + for(uint8_t i = 0; i < bitcounter - 4; i++) { + if(i > 0) { + bits[0] = bits[0] << 1; // | bits[1]>>(32-1); + //bits[1] = bits[1]<<1; + } + if(lfrfid_hitag_worker_validate_command(bits, 5)) return true; + } + } + return false; +} + +static uint8_t lfrfid_hitag_worker_validate_ripple_delta(uint32_t delta) { + if(18 <= delta && delta <= 22) return 1; + if(26 <= delta && delta <= 32) return 1; + return 0; +} + +void lfrfid_hitag_worker_initialize_tag_data(LFRFIDHitag* tag) { + //initialize a tag datastructure + //set all pages to 0x00000000 + memset(tag->pageData, 0, sizeof(tag->pageData)); + + //set all pages to unknown + for(uint8_t i = 0; i < HITAG_PAGES; i++) { + tag->pageKnown[i] = 0; + } + + //default RW settings: + memset(tag->pageRW, READ, HITAG_PAGES / 2 * sizeof(tag->pageRW[0])); + memset(tag->pageRW + HITAG_PAGES / 2, READ | WRITE, HITAG_PAGES / 2 * sizeof(tag->pageRW[0])); + tag->pageRW[2] = 0; + tag->pageRW[3] = 0; + + //default public/secret settings: + for(uint8_t i = 0; i < HITAG_PAGES; i++) { + tag->pagePublic[i] = 1; + } + for(uint8_t i = 2; i <= 31; i++) { + tag->pagePublic[i] = 0; + } +} + +static void lfrfid_hitag_worker_combine_tag_data(LFRFIDHitag* tag) { + memcpy(tag->tagData, tag->pageData, HITAG_PAGES * HITAG_PAGEBYTES); + memcpy(tag->tagData + HITAG_PAGES * HITAG_PAGEBYTES, tag->pageKnown, HITAG_PAGES); +} + +static void lfrfid_hitag_worker_split_tag_data(LFRFIDHitag* tag) { + memcpy(tag->pageData, tag->tagData, HITAG_PAGES * HITAG_PAGEBYTES); + memcpy(tag->pageKnown, tag->tagData + HITAG_PAGES * HITAG_PAGEBYTES, HITAG_PAGES); +} + +void lfrfid_hitag_worker_process_config_page(LFRFIDHitag* tag) { + //RW settings via byte 0 + if(tag->pageData[4 + 0] & 0b10000000) { //block 1 (logData) RW or 0 + memset(tag->pageRW + 1 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); + } else { + memset(tag->pageRW + 1 * 4, 0, 4 * sizeof(tag->pageRW[0])); + } + if(tag->pageData[4 + 0] & 0b01000000) { //key A&B (page 2&3) W or 0 + memset(tag->pageRW + 2, WRITE, 2 * sizeof(tag->pageRW[0])); + } else { + memset(tag->pageRW + 2, 0, 2 * sizeof(tag->pageRW[0])); + } + if(tag->pageData[4 + 0] & 0b00100000) { //block 2 RW or RO + memset(tag->pageRW + 2 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); + } else { + memset(tag->pageRW + 2 * 4, READ, 4 * sizeof(tag->pageRW[0])); + } + if(tag->pageData[4 + 0] & 0b00010000) { //block 3 RW or RO + memset(tag->pageRW + 3 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); + } else { + memset(tag->pageRW + 3 * 4, READ, 4 * sizeof(tag->pageRW[0])); + } + if(tag->pageData[4 + 0] & 0b00001000) { //block 4 RW or RO + memset(tag->pageRW + 4 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); + } else { + memset(tag->pageRW + 4 * 4, READ, 4 * sizeof(tag->pageRW[0])); + } + if(tag->pageData[4 + 0] & 0b00000100) { //block 5 RW or RO + memset(tag->pageRW + 5 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); + } else { + memset(tag->pageRW + 5 * 4, READ, 4 * sizeof(tag->pageRW[0])); + } + if(tag->pageData[4 + 0] & 0b00000010) { //block 6 RW or RO + memset(tag->pageRW + 6 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); + } else { + memset(tag->pageRW + 6 * 4, READ, 4 * sizeof(tag->pageRW[0])); + } + if(tag->pageData[4 + 0] & 0b00000001) { //block 7 RW or RO + memset(tag->pageRW + 7 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); + } else { + memset(tag->pageRW + 7 * 4, READ, 4 * sizeof(tag->pageRW[0])); + } + //RW settings via byte 1 + if(tag->pageData[4 + 1] & 0b00010000) { //config page (1) RW or RO + tag->pageRW[1] = READ / WRITE; + } else { + tag->pageRW[1] = READ; + } + + //public/secret settings via byte 1 + if(tag->pageData[4 + 1] & 0b00000001) { //block 4-7 public or secret + for(uint8_t i = 4 * 4; i < 8 * 4; i++) { + tag->pagePublic[i] = 1; + } + } else { + for(uint8_t i = 4 * 4; i < 8 * 4; i++) { + tag->pagePublic[i] = 0; + } + } +} + +//------------------------------------------------------------------ worker threads ------------------------------------------------------------------ + +static int32_t lfrfid_hitag_worker_emulate_thread(void* thread_context) { + LFRFIDHitagWorker* worker = (LFRFIDHitagWorker*)thread_context; + + //load tag + worker->tag = malloc(sizeof(LFRFIDHitag)); + lfrfid_hitag_worker_initialize_tag_data(worker->tag); + + size_t data_size = protocol_dict_get_data_size(worker->dict, LFRFIDProtocolHitag1); + protocol_dict_get_data(worker->dict, LFRFIDProtocolHitag1, worker->tag->tagData, data_size); + + lfrfid_hitag_worker_split_tag_data(worker->tag); + //TODO: check if a tag has been properly loaded with known serial nr & config page + lfrfid_hitag_worker_process_config_page(worker->tag); + + //init capData for carrier_in + LfRfidHitagCaptureData* capData = malloc(sizeof(LfRfidHitagCaptureData)); + capData->stream = buffer_stream_alloc(EMULATE_BUFFER_SIZE, EMULATE_BUFFER_COUNT); + capData->pair = varint_pair_alloc(); + capData->capCounter = 0; + capData->prevTIMval = 0; + + //init dmaData for emulate mode: + uint16_t dmaLen = 0; + TimerDMAData* dataDMA = malloc(sizeof(TimerDMAData)); + + LfRfidHitagReplyData* dataReply = malloc(sizeof(LfRfidHitagReplyData)); + + //set pins to read setup + furi_hal_rfid_pins_read(); + //reconfigure carrier_out to fixed low state instead + furi_hal_gpio_init(&gpio_rfid_carrier_out, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_rfid_carrier_out, false); + //reconfigure PA15 carrier_in to alt ftn 1 (TIM2 CH1) to use it for input capture + furi_hal_gpio_init_ex( + &gpio_rfid_carrier, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); + //reconfigure pull to alternate function 1 (TIM2 CH3) to drive low (for read carrier) or pull antenna via DMA (for emulation) + furi_hal_gpio_init_ex( + &gpio_nfc_irq_rfid_pull, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedLow, + GpioAltFn1TIM2); + + //start capture via carrier in instead of rfid in + lfrfid_hitag_worker_carrier_in_start( + capData, + worker->carrierPrescaler, + dataDMA->timer_buffer_arr, + dataDMA->timer_buffer_ccr, + dmaLen); + + uint8_t prescaler = worker->carrierPrescaler; + uint8_t period = 8 * prescaler - 1; + + bool carrier_on = false; + int8_t carrier_count = 0; + uint8_t carrier_threshold_on = 5; + int8_t carrier_threshold_off = -5; + + uint32_t periodcounter = 0; + + uint8_t localMax = 0; + uint8_t localMin = 255; + uint32_t localMaxIndex = 0; + uint32_t localMinIndex = 0; + uint32_t lastMaxIndex = 0; + uint32_t lastMinIndex = 0; + uint8_t maxBitCounter = 0; + uint8_t minBitCounter = 0; + uint32_t maxBits[5]; + uint32_t minBits[5]; + int8_t cmd = 0; + uint32_t one = 1 << (32 - 1); + uint32_t cmdIndex = 0; + + uint32_t miniMaxDelta = 0; + uint32_t miniMaxIndex = 0; + uint32_t prevMaxDelta = 0; + uint32_t maxDelta = 0; + + uint32_t miniMinDelta = 0; + uint32_t miniMinIndex = 0; + uint32_t prevMinDelta = 0; + uint32_t minDelta = 0; + + uint32_t prevduration = 0; + uint32_t duration = 0; + uint8_t durationcounter = 0; + uint8_t durationthreshold = 5; + + uint8_t tagState = HitagStateIdle; + uint8_t tagMode = HitagModeBasic; + + while(1) { + Buffer* buffer = buffer_stream_receive(capData->stream, 1); + + if(buffer != NULL) { + size_t buffsize = buffer_get_size(buffer); + uint8_t* buffdata = buffer_get_data(buffer); + + //parse carrier_in duration & scan for cmds + size_t index = 0; + while(index < buffsize) { + prevduration = duration; + size_t tmp_size = + varint_uint32_unpack(&duration, &buffdata[index], buffsize - index); + + if(tmp_size == 0) { + FURI_LOG_E(TAG, "can't unpack varint pair"); + break; + } else { + index += tmp_size; + + periodcounter++; + + if(!carrier_on) { //detect if carrier is available + if(0.75 * period <= duration && duration <= 1.25 * period) { + carrier_count++; + if(carrier_count >= carrier_threshold_on) { + carrier_on = true; + localMax = 0; + localMin = 255; + minBitCounter = 0; + maxBitCounter = 0; + minBits[0] = 0; + minBits[1] = 0; + maxBits[0] = 0; + maxBits[1] = 0; + + carrier_count = 0; + //TODO reset tagstate to Idle & tagMode to basic after power_on_reset (no carrier for 10ms) + } + } else { + carrier_count = 0; + } + } else { + //detect if carrier is still available + if(duration <= 0.75 * period || 1.25 * period <= duration) { + carrier_count--; + if(carrier_count <= carrier_threshold_off) { + carrier_on = false; + carrier_count = 0; + } + } else { + carrier_count = 0; + } + + if(carrier_on) { //if carrier is still available + //finetune the average period for reference + if(prevduration == duration) { + durationcounter++; + if(durationcounter == durationthreshold && period != duration) { + period = duration; + } + } else { + durationcounter = 0; + } + + //detect local MAX, and update maxCmd detection + if(duration > prevduration && duration > period) { + //rising edge above the normal periodduration: update localMax + localMax = duration; + localMaxIndex = periodcounter; + } else if( + duration < prevduration && localMax > period && + periodcounter <= localMaxIndex + 2) { + //we passed a local maximum, derive length between current and prev max index + if(miniMaxDelta == 0) { + prevMaxDelta = maxDelta; + } + maxDelta = (localMaxIndex - lastMaxIndex) * prescaler; + + //check if there's a miniDelta and if it should be added to previous or next delta + if(miniMaxDelta > 0) { + //by default minidelta is added to next + //in case doing this does not result in 2 consequtive valid deltas, but adding it to previous does result in 2 consequtive valid deltas, then assign it to previous delta + if((lfrfid_hitag_worker_validate_ripple_delta( + prevMaxDelta + miniMaxDelta) + + lfrfid_hitag_worker_validate_ripple_delta( + maxDelta - miniMaxDelta)) > + (lfrfid_hitag_worker_validate_ripple_delta(prevMaxDelta) + + lfrfid_hitag_worker_validate_ripple_delta(maxDelta))) { + //update the previous (last) bit from 0 to 1 if a command was ongoing + if(maxBitCounter > 0) { + maxBits[(maxBitCounter - 1) / 32] |= + (one >> (maxBitCounter - 1) % 32); + } + maxDelta -= miniMaxDelta; + lastMaxIndex = miniMaxIndex; + } + miniMaxDelta = 0; + } + + //now process the (possibly updated) delta + if(maxDelta >= 18) { + if(18 <= maxDelta && maxDelta <= 22) { + maxBitCounter++; + } else if(26 <= maxDelta && maxDelta <= 32) { + maxBits[(maxBitCounter) / 32] |= + (one >> (maxBitCounter) % 32); + maxBitCounter++; + } else if(maxBitCounter > 0) { + //end of bitstring caused by invalid delta + + if(lfrfid_hitag_worker_validate_bits( + maxBits, maxBitCounter)) { + cmd = 1; + cmdIndex = localMaxIndex; + + //CMD found, stop searching for minCMD: reset the minCmd detection + minBits[0] = 0; + minBits[1] = 0; + minBitCounter = 0; + } else { + //reset the maxCmd detection + maxBits[0] = 0; + maxBits[1] = 0; + maxBitCounter = 0; + } + } + lastMaxIndex = localMaxIndex; + } else { + miniMaxDelta = maxDelta; + miniMaxIndex = localMaxIndex; + } + localMax = 0; + } + + //terminate bitstring ico absence of next delta (a true end) + if(periodcounter == lastMaxIndex + 40 / prescaler && + maxBitCounter > 0) { + if(lfrfid_hitag_worker_validate_bits(maxBits, maxBitCounter)) { + cmd = 1; + cmdIndex = localMaxIndex; + + //CMD found, stop searching for minCMD: reset the minCmd detection + minBits[0] = 0; + minBits[1] = 0; + minBitCounter = 0; + } else { + //reset the maxCmd detection + maxBits[0] = 0; + maxBits[1] = 0; + maxBitCounter = 0; + } + } + + //detect local MIN, and update minCmd detection + if(duration < prevduration && duration < period) { + //rising edge above the normal periodduration: update localMax + localMin = duration; + localMinIndex = periodcounter; + } else if( + duration > prevduration && localMin < period && + periodcounter <= localMinIndex + 2) { + //we passed a local minimum, derive length between current and prev max index + if(miniMinDelta == 0) { + prevMinDelta = minDelta; + } + minDelta = (localMinIndex - lastMinIndex) * prescaler; + + //check if there's a miniDelta and if it should be added to previous or next delta + if(miniMinDelta > 0) { + //by default minidelta is added to next + //in case doing this does not result in 2 consequtive valid deltas, but adding it to previous does result in 2 consequtive valid deltas, then assign it to previous delta + if((lfrfid_hitag_worker_validate_ripple_delta( + prevMinDelta + miniMinDelta) + + lfrfid_hitag_worker_validate_ripple_delta( + minDelta - miniMinDelta)) > + (lfrfid_hitag_worker_validate_ripple_delta(prevMinDelta) + + lfrfid_hitag_worker_validate_ripple_delta(minDelta))) { + //update the previous (last) bit from 0 to 1 if a command was ongoing + if(minBitCounter > 0) { + minBits[(minBitCounter - 1) / 32] |= + (one >> (minBitCounter - 1) % 32); + } + minDelta -= miniMinDelta; + lastMinIndex = miniMinIndex; + } + miniMinDelta = 0; + } + + //now process the (possibly updated) delta + if(minDelta >= 18) { + if(18 <= minDelta && minDelta <= 22) { + minBitCounter++; + } else if(26 <= minDelta && minDelta <= 32) { + minBits[(minBitCounter) / 32] |= + (one >> (minBitCounter) % 32); + minBitCounter++; + } else if(minBitCounter > 0) { + //end of bitstring caused by invalid delta + if(lfrfid_hitag_worker_validate_bits( + minBits, minBitCounter)) { + cmd = -1; + cmdIndex = localMinIndex; + + //CMD found, stop searching for maxCMD: reset the maxCmd detection + maxBits[0] = 0; + maxBits[1] = 0; + maxBitCounter = 0; + } else { + //reset the minCmd detection + minBits[0] = 0; + minBits[1] = 0; + minBitCounter = 0; + } + } + lastMinIndex = localMinIndex; + } else { + miniMinDelta = minDelta; + miniMinIndex = localMinIndex; + } + localMin = 255; + } + + //terminate of bitstring caused by absence of next delta (a true end) + if(periodcounter == lastMinIndex + 40 / prescaler && + minBitCounter > 0) { + if(lfrfid_hitag_worker_validate_bits(minBits, minBitCounter)) { + cmd = -1; + cmdIndex = localMinIndex; + + //CMD found, stop searching for maxCMD: reset the maxCmd detection + maxBits[0] = 0; + maxBits[1] = 0; + maxBitCounter = 0; + } else { + //reset the minCmd detection + minBits[0] = 0; + minBits[1] = 0; + minBitCounter = 0; + } + } + + //if a valid cmd has been detected: process it & reply + if(cmd != 0) { + uint32_t bits[5]; + uint8_t size = 0; + //select the cmd bit source (max or min detection) + if(cmd == 1) { + bits[0] = maxBits[0]; + bits[1] = maxBits[1]; + size = maxBitCounter; + + maxBits[0] = 0; + maxBits[1] = 0; + maxBitCounter = 0; + } else { + bits[0] = minBits[0]; + bits[1] = minBits[1]; + size = minBitCounter; + + minBits[0] = 0; + minBits[1] = 0; + minBitCounter = 0; + } + cmd = 0; + + dmaLen = 0; + if(size == 5 && (bits[0] >> (32 - 5)) == 0b11001) { //SET_CCNEW + if(tagState != HitagStateQuiet) { + //reset selected state + tagState = HitagStateIdle; + + //set tag in advanced mode + tagMode = HitagModeAdvanced; + + //prepare emulate DMA buffer with advanced SN reply (page 0) + dataReply->replyvalue[0] = 0b00000111; + dataReply->replytype[0] = HITAG_STARTBIT3; + dataReply->replyvalue[1] = worker->tag->pageData[0]; + dataReply->replytype[1] = HITAG_BYTE; + dataReply->replyvalue[2] = worker->tag->pageData[1]; + dataReply->replytype[2] = HITAG_BYTE; + dataReply->replyvalue[3] = worker->tag->pageData[2]; + dataReply->replytype[3] = HITAG_BYTE; + dataReply->replyvalue[4] = worker->tag->pageData[3]; + dataReply->replytype[4] = HITAG_BYTE; + + dataReply->replylength = 5; + + dmaLen = lfrfid_hitag_worker_yield_dma_buffer_AC( + dataReply, dataDMA, worker->carrierPrescaler); + } + } else if(size == 5 && (bits[0] >> (32 - 5)) == 0b00110) { //SET_CC + if(tagState != HitagStateQuiet) { + //reset selected state + tagState = HitagStateIdle; + + //do not (re)set tag in basic mode, this only happens during power on reset + + //prepare emulate DMA buffer with basic SN reply (page 0) + dataReply->replyvalue[0] = 0b00000001; + dataReply->replytype[0] = HITAG_STARTBIT; + dataReply->replyvalue[1] = worker->tag->pageData[0]; + dataReply->replytype[1] = HITAG_BYTE; + dataReply->replyvalue[2] = worker->tag->pageData[1]; + dataReply->replytype[2] = HITAG_BYTE; + dataReply->replyvalue[3] = worker->tag->pageData[2]; + dataReply->replytype[3] = HITAG_BYTE; + dataReply->replyvalue[4] = worker->tag->pageData[3]; + dataReply->replytype[4] = HITAG_BYTE; + + dataReply->replylength = 5; + + dmaLen = lfrfid_hitag_worker_yield_dma_buffer_AC( + dataReply, dataDMA, worker->carrierPrescaler); + } + } else if(size == 45 && (bits[0] >> (32 - 5)) == 0b00000) { //SELECT + //check if SN is matching + uint32_t SN_cmd = bits[0] << 5 | bits[1] >> (32 - 5); + uint32_t SN_tag = worker->tag->pageData[0] << 24 | + worker->tag->pageData[1] << 16 | + worker->tag->pageData[2] << 8 | + worker->tag->pageData[3]; + if(SN_cmd == SN_tag) { + //reply with config (page 1 from tag memory) + tagState = HitagStateSelected; + + //prepare emulate DMA buffer with basic/advanced config page reply (page 1) + if(tagMode == HitagModeBasic) { + dataReply->replyvalue[0] = 0b00000001; + dataReply->replytype[0] = HITAG_STARTBIT; + + dataReply->replyvalue[1] = + worker->tag->pageData[4 + 0]; + dataReply->replytype[1] = HITAG_BYTE; + dataReply->replyvalue[2] = + worker->tag->pageData[4 + 1]; + dataReply->replytype[2] = HITAG_BYTE; + dataReply->replyvalue[3] = + worker->tag->pageData[4 + 2]; + dataReply->replytype[3] = HITAG_BYTE; + dataReply->replyvalue[4] = + worker->tag->pageData[4 + 3]; + dataReply->replytype[4] = HITAG_BYTE; + + dataReply->replylength = 5; + } else if(tagMode == HitagModeAdvanced) { + dataReply->replyvalue[0] = 0b00111111; + dataReply->replytype[0] = HITAG_STARTBIT6; + + dataReply->replyvalue[1] = + worker->tag->pageData[4 + 0]; + dataReply->replytype[1] = HITAG_BYTE; + dataReply->replyvalue[2] = + worker->tag->pageData[4 + 1]; + dataReply->replytype[2] = HITAG_BYTE; + dataReply->replyvalue[3] = + worker->tag->pageData[4 + 2]; + dataReply->replytype[3] = HITAG_BYTE; + dataReply->replyvalue[4] = + worker->tag->pageData[4 + 3]; + dataReply->replytype[4] = HITAG_BYTE; + + uint8_t crc = CRC_PRESET; + lfrfid_hitag_worker_calc_crc( + &crc, dataReply->replyvalue[1], 8); + lfrfid_hitag_worker_calc_crc( + &crc, dataReply->replyvalue[2], 8); + lfrfid_hitag_worker_calc_crc( + &crc, dataReply->replyvalue[3], 8); + lfrfid_hitag_worker_calc_crc( + &crc, dataReply->replyvalue[4], 8); + + dataReply->replyvalue[5] = crc; + dataReply->replytype[5] = HITAG_CRC; + + dataReply->replylength = 6; + } + + dmaLen = lfrfid_hitag_worker_yield_dma_buffer_MC( + dataReply, dataDMA, worker->carrierPrescaler); + } + } else if(size == 20 && (bits[0] >> (31))) { //R/W + if(tagState == HitagStateSelected) { + //reply with requested pages in case of read public (ignore secret block/pages & ignore write cmd) + uint8_t rwCmd = bits[0] >> (32 - 4); + uint8_t addr = bits[0] >> 20 & 0xff; + uint8_t pages = 0; + switch(rwCmd) { + case(0b1100): + //read public page + if(worker->tag->pageKnown[addr] && + worker->tag->pagePublic[addr]) { + pages = 1; + } + break; + case(0b1101): + //read public block + pages = (64 - addr) % 4; + if(pages == 0) { + pages = 4; + } + for(uint8_t p = addr; p < addr + pages; p++) { + if(!worker->tag->pageKnown[p] || + !worker->tag->pagePublic[p]) { + pages = 0; + break; + } + } + break; + } + if(pages > 0) { + //pages + for(uint8_t i = 0; i < pages; i++) { + dataReply->replyvalue[1 + i * 4 + 0] = + worker->tag->pageData[(addr + i) * 4 + 0]; + dataReply->replytype[1 + i * 4 + 0] = HITAG_BYTE; + dataReply->replyvalue[1 + i * 4 + 1] = + worker->tag->pageData[(addr + i) * 4 + 1]; + dataReply->replytype[1 + i * 4 + 1] = HITAG_BYTE; + dataReply->replyvalue[1 + i * 4 + 2] = + worker->tag->pageData[(addr + i) * 4 + 2]; + dataReply->replytype[1 + i * 4 + 2] = HITAG_BYTE; + dataReply->replyvalue[1 + i * 4 + 3] = + worker->tag->pageData[(addr + i) * 4 + 3]; + dataReply->replytype[1 + i * 4 + 3] = HITAG_BYTE; + } + //startsequence & CRC + if(tagMode == HitagModeBasic) { + dataReply->replyvalue[0] = 0b00000001; + dataReply->replytype[0] = HITAG_STARTBIT; + + dataReply->replylength = 1 + pages * 4; + } else if(tagMode == HitagModeAdvanced) { + dataReply->replyvalue[0] = 0b00111111; + dataReply->replytype[0] = HITAG_STARTBIT6; + + uint8_t crc = CRC_PRESET; + + for(uint8_t i = 0; i < pages; i++) { + lfrfid_hitag_worker_calc_crc( + &crc, + dataReply->replyvalue[1 + i * 4 + 0], + 8); + lfrfid_hitag_worker_calc_crc( + &crc, + dataReply->replyvalue[1 + i * 4 + 1], + 8); + lfrfid_hitag_worker_calc_crc( + &crc, + dataReply->replyvalue[1 + i * 4 + 2], + 8); + lfrfid_hitag_worker_calc_crc( + &crc, + dataReply->replyvalue[1 + i * 4 + 3], + 8); + } + + dataReply->replyvalue[1 + pages * 4] = crc; + dataReply->replytype[1 + pages * 4] = HITAG_CRC; + + dataReply->replylength = 2 + pages * 4; + } + + dmaLen = lfrfid_hitag_worker_yield_dma_buffer_MC( + dataReply, dataDMA, worker->carrierPrescaler); + } + } + } else if(size == 20 && (bits[0] >> (32 - 4)) == 0b0111) { //HALT + //TODO validate if dummy address is a valid one (one of the default public pages (32-63)) + + //acknowledge & go silent untill next reset + if(tagState == HitagStateSelected) { + tagState = HitagStateQuiet; + + if(tagMode == HitagModeBasic) { + dataReply->replyvalue[0] = 0b00000101; + dataReply->replytype[0] = HITAG_ACK; + } else if(tagMode == HitagModeAdvanced) { + dataReply->replyvalue[0] = 0b11111101; + dataReply->replytype[0] = HITAG_ACK_ADV; + } + + dataReply->replylength = 1; + + dmaLen = lfrfid_hitag_worker_yield_dma_buffer_MC( + dataReply, dataDMA, worker->carrierPrescaler); + } + } + + if(dmaLen > 0) { //switch to reply mode + //switch carrier mode to ETR required for emulation + lfrfid_hitag_worker_carrier_in_ETR_mode(capData, prescaler); + + //respect hitag WAIT1 time + uint32_t wait1_periods = 180 / prescaler; + while(capData->capCounter - cmdIndex < wait1_periods) { + furi_delay_us(40); + } + + //just start the DMA, stopping is handled in DMA transfer complete interrupt + lfrfid_hitag_worker_pull_out_dma_start((size_t)dmaLen); + } + } + } + } + } + } + + //reset buffer + buffer_reset(buffer); + } + + if(buffer_stream_get_overrun_count(capData->stream) > 0) { + FURI_LOG_E(TAG, "Read overrun, recovering"); + buffer_stream_reset(capData->stream); + } + + uint32_t flags = furi_event_flag_get(worker->events); + if(FURI_BIT(flags, LFRFIDHitagWorkerSignalStop)) { + break; + } + } + + //stop carrier capture in + lfrfid_hitag_worker_carrier_in_stop(); + + free(worker->tag); + free(dataDMA); + free(dataReply); + varint_pair_free(capData->pair); + buffer_stream_free(capData->stream); + free(capData); + + //clear the stop flag on workder so that i can use the worker to launch a new read routine without having to restart the app + furi_event_flag_clear(worker->events, 1 << LFRFIDHitagWorkerSignalStop); + + return 0; +} + +static int32_t lfrfid_hitag_worker_read_thread(void* thread_context) { + LFRFIDHitagWorker* worker = (LFRFIDHitagWorker*)thread_context; + + //initialize a tag datastructure + worker->tag = malloc(sizeof(LFRFIDHitag)); + lfrfid_hitag_worker_initialize_tag_data(worker->tag); + + //prep variables for decoder + LfRfidHitagProtocol currentProtocol = HitagProtocolAntiCollision; + uint8_t startBits = 1; + uint32_t memBlock[HITAG_BLOCKPAGES] = {0}; + uint8_t bits = 0; + bool halfBit = false; + bool finished = false; + + //prep variables for worker + uint8_t expectedBits = 33; + uint8_t readings = 0; + uint8_t currPage = 0; + bool readSucces = false; + uint8_t readThreshold = 3; + + // setup pins for reading + furi_hal_rfid_pins_read(); + + //and setup TIM1 CH1N with DMA + //fill the DMA arr & ccr buffers + /*start with looping SETCC command*/ + LfRfidHitagCMDData* dataCMD = malloc(sizeof(LfRfidHitagCMDData)); + dataCMD->CMDlength = 0; + dataCMD->CMDposition = 0; + dataCMD->CMDsubposition = 0; + dataCMD->CMDactive = false; + + LfRfidHitagCMDData* backupCMD = malloc(sizeof(LfRfidHitagCMDData)); + uint32_t value[] = { + 128, + 0b00110, + 1, + 213, + (uint32_t)(1.05 * 33 * HITAG_BITPERIODS_AC)}; //always take 5% reading time buffer + uint8_t type[] = {HITAG_ON, HITAG_SET, HITAG_STOP, HITAG_ON, HITAG_ON}; + for(size_t i = 0; i < 5; i++) { + backupCMD->CMDvalue[i] = value[i]; + backupCMD->CMDtype[i] = type[i]; + } + backupCMD->CMDlength = 5; + backupCMD->CMDposition = 0; + backupCMD->CMDsubposition = 0; + backupCMD->CMDloop = + true; //not really required since by definition the backup cmd is looping already in yield code + backupCMD->CMDactive = true; + + TimerDMAData* dataDMA = malloc(sizeof(TimerDMAData)); + lfrfid_hitag_worker_yield_dma_buffer_BPLM(dataCMD, backupCMD, dataDMA, 0, DMA_BUFFER_SIZE / 2); + lfrfid_hitag_worker_yield_dma_buffer_BPLM( + dataCMD, backupCMD, dataDMA, DMA_BUFFER_SIZE / 2, DMA_BUFFER_SIZE / 2); + + //start TIM1 with DMA function + lfrfid_hitag_worker_carrier_out_start( + dataDMA->timer_buffer_arr, dataDMA->timer_buffer_ccr, DMA_BUFFER_SIZE, worker); + + // start capture + LfRfidHitagCaptureData* capData = malloc(sizeof(LfRfidHitagCaptureData)); + capData->stream = buffer_stream_alloc(READ_BUFFER_SIZE, READ_BUFFER_COUNT); + capData->pair = varint_pair_alloc(); + furi_hal_rfid_tim_read_capture_start(lfrfid_hitag_worker_capture_in_cc_isr, capData); + + while(1) { + Buffer* buffer = buffer_stream_receive(capData->stream, 1); + + //process data if available + if(buffer != NULL) { + size_t buffsize = buffer_get_size(buffer); + uint8_t* buffdata = buffer_get_data(buffer); + + //if scanning for tagdata, then also parse buffer data + size_t index = 0; + while(expectedBits > 0 && index < buffsize) { + uint32_t duration; + uint32_t pulse; + size_t tmp_size; + + if(!varint_pair_unpack( + &buffdata[index], buffsize - index, &pulse, &duration, &tmp_size)) { + FURI_LOG_E(TAG, "can't unpack varint pair"); + break; + } else { + index += tmp_size; + + //feed pulse duration to decoder + lfrfid_hitag_worker_decoder_feed( + pulse, + duration, + memBlock, + &bits, + &halfBit, + &finished, + currentProtocol, + startBits); + + //see if i have a valid bit sequence + if(finished) { + //bitsequence ready for validation + if(bits == + expectedBits) { //eligable bitsequence detected, accept it only after x consequtive readings + //reset the backupCMD count to ensure some pause between bitsequence detection & the CMD injection + backupCMD->CMDcount = 0; + + if(readings == 0) { + for(uint8_t p = 0; p < (expectedBits - 1) / 32; p++) { + worker->tag->pageData[(currPage + p) * 4 + 0] = + memBlock[p] >> 24 & 0xff; + worker->tag->pageData[(currPage + p) * 4 + 1] = + memBlock[p] >> 16 & 0xff; + worker->tag->pageData[(currPage + p) * 4 + 2] = + memBlock[p] >> 8 & 0xff; + worker->tag->pageData[(currPage + p) * 4 + 3] = memBlock[p] & + 0xff; + worker->tag->pageKnown[currPage + p] = 0; + } + readings = 1; + } else if(readings < readThreshold) { + bool match = true; + for(uint8_t p = 0; p < (expectedBits - 1) / 32; p++) { + if(worker->tag->pageData[(currPage + p) * 4 + 0] != + (memBlock[p] >> 24 & 0xff) || + worker->tag->pageData[(currPage + p) * 4 + 1] != + (memBlock[p] >> 16 & 0xff) || + worker->tag->pageData[(currPage + p) * 4 + 2] != + (memBlock[p] >> 8 & 0xff) || + worker->tag->pageData[(currPage + p) * 4 + 3] != + (memBlock[p] & 0xff)) { + match = false; + break; + } + } + if(match) { + readings++; + if(readings == readThreshold) { + //succesful page(s) reading + readSucces = true; + + for(uint8_t p = 0; p < (expectedBits - 1) / 32; p++) { + worker->tag->pageKnown[currPage + p] = 1; + } + + readings = 0; + expectedBits = 0; //to prevent further updates to tag data + } + } else { + for(uint8_t p = 0; p < (expectedBits - 1) / 32; p++) { + worker->tag->pageData[(currPage + p) * 4 + 0] = + memBlock[p] >> 24 & 0xff; + worker->tag->pageData[(currPage + p) * 4 + 1] = + memBlock[p] >> 16 & 0xff; + worker->tag->pageData[(currPage + p) * 4 + 2] = + memBlock[p] >> 8 & 0xff; + worker->tag->pageData[(currPage + p) * 4 + 3] = + memBlock[p] & 0xff; + worker->tag->pageKnown[currPage + p] = 0; + } + readings = 1; + } + } + } + //reset tempdata for next bitsequence readout + memset(memBlock, 0, HITAG_BLOCKPAGES * sizeof(memBlock[0])); + bits = 0; + finished = false; + } + } + } + + //reset buffer + buffer_reset(buffer); + } + + //if bitsequence found & after pause, prep & launch the command for reading out next bit of data + if(readSucces && backupCMD->CMDcount >= 7) { + readSucces = false; + backupCMD->CMDcount = + 0; //required since otherwise the 'relaunch' of cmd is triggered straightaway + + //update protocolData + size_t data_size = protocol_dict_get_data_size(worker->dict, LFRFIDProtocolHitag1); + lfrfid_hitag_worker_combine_tag_data(worker->tag); + protocol_dict_set_data( + worker->dict, LFRFIDProtocolHitag1, worker->tag->tagData, data_size); + + if(currPage == 0) { //read config page next via SELECT cmd + FURI_LOG_D( + TAG, + "Hitag1, [%02X %02X %02X %02X] found", + worker->tag->pageData[0], + worker->tag->pageData[1], + worker->tag->pageData[2], + worker->tag->pageData[3]); + DOLPHIN_DEED(DolphinDeedRfidReadSuccess); + worker->workerstatus = LFRFIDHitagStatusDetected; + + currentProtocol = HitagProtocolManchesterCoding; + expectedBits = 33; + currPage = 1; + } else if(currPage == 1) { //process config page and start reading public blocks + lfrfid_hitag_worker_process_config_page(worker->tag); + FURI_LOG_D( + TAG, + "Hitag1, [%02X %02X %02X %02X] config page read", + worker->tag->pageData[0], + worker->tag->pageData[1], + worker->tag->pageData[2], + worker->tag->pageData[3]); + //find next public readible block + for(currPage = 4; currPage <= HITAG_PAGES - 4; currPage += 4) { + if(worker->tag->pagePublic[currPage] && + (worker->tag->pageRW[currPage] & READ)) { //public readible page/block found + expectedBits = 129; + break; + } else { + FURI_LOG_D( + TAG, + "Hitag1, [%02X %02X %02X %02X] block %u not public/readible", + worker->tag->pageData[0], + worker->tag->pageData[1], + worker->tag->pageData[2], + worker->tag->pageData[3], + currPage / 4); + } + } + } else if(currPage < HITAG_PAGES - 4) { //scan for next public readible page + FURI_LOG_D( + TAG, + "Hitag1, [%02X %02X %02X %02X] block %u read", + worker->tag->pageData[0], + worker->tag->pageData[1], + worker->tag->pageData[2], + worker->tag->pageData[3], + currPage / 4); + for(currPage += 4; currPage <= HITAG_PAGES - 4; currPage += 4) { + if(worker->tag->pagePublic[currPage] && + (worker->tag->pageRW[currPage] & READ)) { //public readible page/block found + expectedBits = 129; + break; + } else { + FURI_LOG_D( + TAG, + "Hitag1, [%02X %02X %02X %02X] block %u not public/readible", + worker->tag->pageData[0], + worker->tag->pageData[1], + worker->tag->pageData[2], + worker->tag->pageData[3], + currPage / 4); + } + } + } else if( + currPage == + HITAG_PAGES - + 4) { //all public readible pages read: signal view & break out of while loop (stops the worker) + FURI_LOG_D( + TAG, + "Hitag1, [%02X %02X %02X %02X] block %u read", + worker->tag->pageData[0], + worker->tag->pageData[1], + worker->tag->pageData[2], + worker->tag->pageData[3], + currPage / 4); + + worker->workerstatus = LFRFIDHitagStatusRead; + break; + } + + if(expectedBits > 0) { //inject CMD + //prep SELECT cmd bytes + uint8_t SN0 = worker->tag->pageData[0]; + uint8_t SN1 = worker->tag->pageData[1]; + uint8_t SN2 = worker->tag->pageData[2]; + uint8_t SN3 = worker->tag->pageData[3]; + uint8_t crc = CRC_PRESET; + lfrfid_hitag_worker_calc_crc(&crc, (0b00000 << (8 - 5)), 5); + lfrfid_hitag_worker_calc_crc(&crc, SN0, 8); + lfrfid_hitag_worker_calc_crc(&crc, SN1, 8); + lfrfid_hitag_worker_calc_crc(&crc, SN2, 8); + lfrfid_hitag_worker_calc_crc(&crc, SN3, 8); + + //prep SELECT cmd array + uint32_t value[] = { + 128, + 0b00000, + SN0, + SN1, + SN2, + SN3, + crc, + 1, + 213, + (uint32_t)(1.05 * 33 * HITAG_BITPERIODS_MC)}; //always take 5% reading time buffer + uint8_t type[] = { + HITAG_ON, + HITAG_SELECT, + HITAG_BYTE, + HITAG_BYTE, + HITAG_BYTE, + HITAG_BYTE, + HITAG_CRC, + HITAG_STOP, + HITAG_ON, + HITAG_ON}; + uint16_t len = 10; + uint16_t cnt = 1; + for(size_t i = 0; i < cnt * len; i++) { + dataCMD->CMDvalue[i] = value[i % len]; + dataCMD->CMDtype[i] = type[i % len]; + } + dataCMD->CMDlength = cnt * len; + dataCMD->CMDposition = 0; + dataCMD->CMDsubposition = 0; + dataCMD->CMDloop = false; + + if(currPage < 4 * 4) { //add READPAGE cmd to array + crc = CRC_PRESET; + lfrfid_hitag_worker_calc_crc(&crc, (0b1100 << (8 - 4)), 4); + lfrfid_hitag_worker_calc_crc(&crc, currPage, 8); + uint32_t value2[] = { + 128, + 0b1100, + currPage, + crc, + 1, + 213, + (uint32_t)(1.05 * 33 * HITAG_BITPERIODS_MC)}; //always take 5% reading time buffer + uint8_t type2[] = { + HITAG_ON, HITAG_CMD, HITAG_BYTE, HITAG_CRC, HITAG_STOP, HITAG_ON, HITAG_ON}; + uint16_t len2 = 7; + uint16_t cnt2 = 5; + for(size_t i = 0; i < cnt2 * len2; i++) { + dataCMD->CMDvalue[cnt * len + i] = value2[i % len2]; + dataCMD->CMDtype[cnt * len + i] = type2[i % len2]; + } + dataCMD->CMDlength += cnt2 * len2; + } else { //add READBLOCK cmd to array + crc = CRC_PRESET; + lfrfid_hitag_worker_calc_crc(&crc, (0b1101 << (8 - 4)), 4); + lfrfid_hitag_worker_calc_crc(&crc, currPage, 8); + uint32_t value2[] = { + 128, + 0b1101, + currPage, + crc, + 1, + 213, + (uint32_t)(1.05 * 129 * HITAG_BITPERIODS_MC)}; //always take 5% reading time buffer + uint8_t type2[] = { + HITAG_ON, HITAG_CMD, HITAG_BYTE, HITAG_CRC, HITAG_STOP, HITAG_ON, HITAG_ON}; + uint16_t len2 = 7; + uint16_t cnt2 = 5; + for(size_t i = 0; i < cnt2 * len2; i++) { + dataCMD->CMDvalue[cnt * len + i] = value2[i % len2]; + dataCMD->CMDtype[cnt * len + i] = type2[i % len2]; + } + dataCMD->CMDlength += cnt2 * len2; + } + } + } + + //while not having x consequtive reads, relaunch the (already prepared) command + if(!readSucces && backupCMD->CMDcount >= 7 && currPage > 0 && expectedBits > 0) { + backupCMD->CMDcount = 0; + dataCMD->CMDposition = 0; + dataCMD->CMDsubposition = 0; + } + + if(buffer_stream_get_overrun_count(capData->stream) > 0) { + FURI_LOG_E(TAG, "Read overrun, recovering"); + buffer_stream_reset(capData->stream); + } + + //check for stop events + uint32_t flags = furi_event_flag_get(worker->events); + if(FURI_BIT(flags, LFRFIDHitagWorkerSignalStop)) { + break; + } + + //check for DMA HT/TC signals to repopulate the DMA buffer + if(worker->DMAeventCount > 1) { + FURI_LOG_E(TAG, "DMA refilling can't keep up %u", worker->DMAeventCount); + } + if(FURI_BIT(flags, LFRFIDHitagWorkerSignalHalfTransfer)) { + //refill first half of dma buffer + lfrfid_hitag_worker_yield_dma_buffer_BPLM( + dataCMD, backupCMD, dataDMA, 0, DMA_BUFFER_SIZE / 2); + } + if(FURI_BIT(flags, LFRFIDHitagWorkerSignalTransferComplete)) { + //refill second half of dma buffer + lfrfid_hitag_worker_yield_dma_buffer_BPLM( + dataCMD, backupCMD, dataDMA, DMA_BUFFER_SIZE / 2, DMA_BUFFER_SIZE / 2); + } + worker->DMAeventCount = 0; + //clear all flags (i have processed all known/expected events) + furi_event_flag_clear(worker->events, flags); + } + + //stop timers + furi_hal_rfid_tim_read_capture_stop(); + lfrfid_hitag_worker_carrier_out_stop(); + + //free memory + free(dataCMD); + free(backupCMD); + free(dataDMA); + varint_pair_free(capData->pair); + buffer_stream_free(capData->stream); + free(capData); + + //allow the view that started the thread to signal the stop, which will properly merge the threads & de-init the worker + if(currPage == HITAG_PAGES - 4) { + const uint32_t available_flags = (1 << LFRFIDHitagWorkerSignalStop); + while(true) { + uint32_t flags = furi_event_flag_wait( + worker->events, available_flags, FuriFlagWaitAny, FuriWaitForever); + + if(FURI_BIT(flags, LFRFIDHitagWorkerSignalStop)) { + break; + } + } + } + + free(worker->tag); + + //clear the stop flag on workder so that i can use the same worker to launch a new routine + furi_event_flag_clear(worker->events, 1 << LFRFIDHitagWorkerSignalStop); + + return 0; +} diff --git a/lib/lfrfid/lfrfid_hitag_worker.h b/lib/lfrfid/lfrfid_hitag_worker.h new file mode 100644 index 000000000..9817991b8 --- /dev/null +++ b/lib/lfrfid/lfrfid_hitag_worker.h @@ -0,0 +1,86 @@ +#pragma once +#include +#include +#include +#include +#include +#include +//#include +#include "protocols/lfrfid_protocols.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include + +#define HITAG_BLOCKS 16 +#define HITAG_BLOCKPAGES 4 +#define HITAG_PAGES 64 +#define HITAG_PAGEBYTES 4 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + LFRFIDHitagWorkerSettingRead, + LFRFIDHitagWorkerSettingEmulate, +} LFRFIDHitagWorkerSetting; + +typedef enum { + LFRFIDHitagStatusScanning, + LFRFIDHitagStatusDetected, + LFRFIDHitagStatusRead, +} LFRFIDHitagStatus; + +typedef struct LFRFIDHitagWorker LFRFIDHitagWorker; + +/** + * @brief Get the tag read status + * + * @return tag read status + */ +LFRFIDHitagStatus lfrfid_hitag_worker_get_status(LFRFIDHitagWorker* worker); + +/** + * @brief Allocate a new LFRFIDHitagWorker instance + * + * @return LFRFIDHitagWorker* + */ +LFRFIDHitagWorker* lfrfid_hitag_worker_alloc(ProtocolDict* dict); + +/** + * @brief Free a LFRFIDHitagWorker instance + * + * @param worker LFRFIDHitagWorker instance + */ +void lfrfid_hitag_worker_free(LFRFIDHitagWorker* worker); + +/** + * @brief Start hitag worker from own generated field + * + * @param worker LFRFIDHitagWorker instance + * @param setting read/emulate + */ +void lfrfid_hitag_worker_start(LFRFIDHitagWorker* worker, LFRFIDHitagWorkerSetting setting); + +/** + * @brief Stop worker + * + * @param worker + */ +void lfrfid_hitag_worker_stop(LFRFIDHitagWorker* worker); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/lfrfid/lfrfid_worker.h b/lib/lfrfid/lfrfid_worker.h index def9f89a4..682b077a0 100644 --- a/lib/lfrfid/lfrfid_worker.h +++ b/lib/lfrfid/lfrfid_worker.h @@ -23,6 +23,7 @@ typedef enum { LFRFIDWorkerReadTypeAuto, LFRFIDWorkerReadTypeASKOnly, LFRFIDWorkerReadTypePSKOnly, + LFRFIDWorkerReadTypeRTFOnly, } LFRFIDWorkerReadType; typedef enum { @@ -32,6 +33,8 @@ typedef enum { LFRFIDWorkerReadSenseCardEnd, LFRFIDWorkerReadStartASK, LFRFIDWorkerReadStartPSK, + LFRFIDWorkerReadStartRTF, + LFRFIDWorkerReadSenseHitag, //TODO combine with sense carstart? LFRFIDWorkerReadDone, } LFRFIDWorkerReadResult; diff --git a/lib/lfrfid/lfrfid_worker_i.h b/lib/lfrfid/lfrfid_worker_i.h index 33c0bff08..d46ef2bba 100644 --- a/lib/lfrfid/lfrfid_worker_i.h +++ b/lib/lfrfid/lfrfid_worker_i.h @@ -8,6 +8,7 @@ #include #include "lfrfid_worker.h" #include "lfrfid_raw_worker.h" +#include "lfrfid_hitag_worker.h" #include "protocols/lfrfid_protocols.h" #ifdef __cplusplus diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index 9b6f16eb1..c716c0d09 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -94,7 +94,7 @@ typedef enum { LFRFIDWorkerReadTimeout, } LFRFIDWorkerReadState; -static LFRFIDWorkerReadState lfrfid_worker_read_internal( +static LFRFIDWorkerReadState lfrfid_worker_read_ttf( //tag talks first LFRFIDWorker* worker, LFRFIDFeature feature, uint32_t timeout, @@ -334,6 +334,63 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal( return state; } +static LFRFIDWorkerReadState lfrfid_worker_read_rtf( //reader talks first + LFRFIDWorker* worker, + LFRFIDFeature feature, + uint32_t timeout, + ProtocolId* result_protocol) { + UNUSED(feature); + LFRFIDWorkerReadState state = LFRFIDWorkerReadTimeout; + + FURI_LOG_D(TAG, "Start RTF"); + if(worker->read_cb) { + worker->read_cb(LFRFIDWorkerReadStartRTF, PROTOCOL_NO, worker->cb_ctx); + } + LFRFIDHitagWorker* hitag_worker = lfrfid_hitag_worker_alloc(worker->protocols); + + lfrfid_hitag_worker_start(hitag_worker, LFRFIDHitagWorkerSettingRead); + + FURI_LOG_D(TAG, "Read started"); + + //scan for hitag for a while and stay in hitag mode if card was detected + uint8_t delays = 0; + uint8_t delay_ms = 100; + bool notified = false; + while(1) { + furi_delay_ms(delay_ms); + + if(lfrfid_worker_check_for_stop(worker)) { + state = LFRFIDWorkerReadExit; + *result_protocol = PROTOCOL_NO; + break; + } + + if(lfrfid_hitag_worker_get_status(hitag_worker) == LFRFIDHitagStatusDetected) { + *result_protocol = + LFRFIDProtocolHitag1; //TODO get protocol ID from hitag_worker when expanding the worker to include other hitag protocols + if(!notified && worker->read_cb) { + worker->read_cb(LFRFIDWorkerReadSenseHitag, *result_protocol, worker->cb_ctx); + notified = true; + } + } else if(lfrfid_hitag_worker_get_status(hitag_worker) == LFRFIDHitagStatusRead) { + state = LFRFIDWorkerReadOK; + *result_protocol = + LFRFIDProtocolHitag1; //TODO get protocol ID from hitag_worker when expanding the worker to include other hitag protocols + break; + } else if(++delays >= timeout / delay_ms) { + state = LFRFIDWorkerReadTimeout; + break; + } + } + + lfrfid_hitag_worker_stop(hitag_worker); + lfrfid_hitag_worker_free(hitag_worker); + + FURI_LOG_D(TAG, "Read stopped"); + + return state; +} + static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { ProtocolId read_result = PROTOCOL_NO; LFRFIDWorkerReadState state; @@ -341,6 +398,8 @@ static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { if(worker->read_type == LFRFIDWorkerReadTypePSKOnly) { feature = LFRFIDFeaturePSK; + } else if(worker->read_type == LFRFIDWorkerReadTypeRTFOnly) { + feature = LFRFIDFeatureRTF; } else { feature = LFRFIDFeatureASK; } @@ -348,8 +407,14 @@ static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { if(worker->read_type == LFRFIDWorkerReadTypeAuto) { while(1) { // read for a while - state = lfrfid_worker_read_internal( - worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); + + if(feature == LFRFIDFeatureASK || feature == LFRFIDFeaturePSK) { + state = lfrfid_worker_read_ttf( + worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); + } else if(feature == LFRFIDFeatureRTF) { + state = lfrfid_worker_read_rtf( + worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); + } if(state == LFRFIDWorkerReadOK || state == LFRFIDWorkerReadExit) { break; @@ -358,7 +423,9 @@ static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { // switch to next feature if(feature == LFRFIDFeatureASK) { feature = LFRFIDFeaturePSK; - } else { + } else if(feature == LFRFIDFeaturePSK) { + feature = LFRFIDFeatureRTF; + } else if(feature == LFRFIDFeatureRTF) { feature = LFRFIDFeatureASK; } @@ -367,10 +434,12 @@ static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { } else { while(1) { if(worker->read_type == LFRFIDWorkerReadTypeASKOnly) { - state = lfrfid_worker_read_internal(worker, feature, UINT32_MAX, &read_result); - } else { - state = lfrfid_worker_read_internal( + state = lfrfid_worker_read_ttf(worker, feature, UINT32_MAX, &read_result); + } else if(worker->read_type == LFRFIDWorkerReadTypePSKOnly) { + state = lfrfid_worker_read_ttf( worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); + } else { + state = lfrfid_worker_read_rtf(worker, feature, UINT32_MAX, &read_result); } if(state == LFRFIDWorkerReadOK || state == LFRFIDWorkerReadExit) { @@ -406,7 +475,7 @@ static void lfrfid_worker_emulate_dma_isr(bool half, void* context) { furi_stream_buffer_send(stream, &flag, sizeof(uint32_t), 0); } -static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) { +static void lfrfid_worker_emulate_ttf(LFRFIDWorker* worker) { LFRFIDWorkerEmulateBuffer* buffer = malloc(sizeof(LFRFIDWorkerEmulateBuffer)); FuriStreamBuffer* stream = furi_stream_buffer_alloc(sizeof(uint32_t), sizeof(uint32_t)); LFRFIDProtocol protocol = worker->protocol; @@ -495,6 +564,39 @@ static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) { pulse_glue_free(pulse_glue); } +static void lfrfid_worker_emulate_rtf(LFRFIDWorker* worker) { + LFRFIDHitagWorker* hitag_worker = lfrfid_hitag_worker_alloc( + worker->protocols); //todo, pass protocols & protocol id when expanding the worker to include other hitag protocols + + lfrfid_hitag_worker_start(hitag_worker, LFRFIDHitagWorkerSettingEmulate); + uint8_t delay_ms = 100; + while(1) { + furi_delay_ms(delay_ms); + + if(lfrfid_worker_check_for_stop(worker)) { + break; + } + } + + lfrfid_hitag_worker_stop(hitag_worker); + lfrfid_hitag_worker_free(hitag_worker); +} + +static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) { + if(worker != NULL) { + if(worker->protocols != NULL) { + LFRFIDFeature feature = + protocol_dict_get_features(worker->protocols, worker->protocol); + + if(feature == LFRFIDFeatureRTF) { + lfrfid_worker_emulate_rtf(worker); + } else { + lfrfid_worker_emulate_ttf(worker); + } + } + } +} + /**************************************************************************************************/ /********************************************* WRITE **********************************************/ /**************************************************************************************************/ @@ -521,7 +623,7 @@ static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) { t5577_write(&request->t5577); ProtocolId read_result = PROTOCOL_NO; - LFRFIDWorkerReadState state = lfrfid_worker_read_internal( + LFRFIDWorkerReadState state = lfrfid_worker_read_ttf( worker, protocol_dict_get_features(worker->protocols, protocol), LFRFID_WORKER_WRITE_VERIFY_TIME_MS, diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 2c1f0ad97..43d59e123 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -16,6 +16,7 @@ #include "protocol_pac_stanley.h" #include "protocol_keri.h" #include "protocol_gallagher.h" +#include "protocol_hitag1.h" const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, @@ -35,4 +36,5 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolPACStanley] = &protocol_pac_stanley, [LFRFIDProtocolKeri] = &protocol_keri, [LFRFIDProtocolGallagher] = &protocol_gallagher, + [LFRFIDProtocolHitag1] = &protocol_hitag1, }; \ No newline at end of file diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 848f003a3..6d0e3fa4e 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -5,6 +5,7 @@ typedef enum { LFRFIDFeatureASK = 1 << 0, /** ASK Demodulation */ LFRFIDFeaturePSK = 1 << 1, /** PSK Demodulation */ + LFRFIDFeatureRTF = 1 << 2, /** Reader Talks First: ASK Demodulation with 2 way communication */ } LFRFIDFeature; typedef enum { @@ -25,6 +26,7 @@ typedef enum { LFRFIDProtocolPACStanley, LFRFIDProtocolKeri, LFRFIDProtocolGallagher, + LFRFIDProtocolHitag1, LFRFIDProtocolMax, } LFRFIDProtocol; diff --git a/lib/lfrfid/protocols/protocol_hitag1.c b/lib/lfrfid/protocols/protocol_hitag1.c new file mode 100644 index 000000000..1890ea1ad --- /dev/null +++ b/lib/lfrfid/protocols/protocol_hitag1.c @@ -0,0 +1,104 @@ +#include +#include +#include "lfrfid_protocols.h" + +#define HITAG1_PAGES 64 +#define HITAG1_DATA_SIZE HITAG1_PAGES * 4 + HITAG1_PAGES + +typedef struct { + uint8_t tagData[HITAG1_DATA_SIZE]; +} ProtocolHitag1; + +ProtocolHitag1* protocol_hitag1_alloc(void) { + ProtocolHitag1* protocol = malloc(sizeof(ProtocolHitag1)); + + return protocol; +}; + +void protocol_hitag1_free(ProtocolHitag1* protocol) { + free(protocol); +}; + +uint8_t* protocol_hitag1_get_data(ProtocolHitag1* protocol) { + return protocol->tagData; +}; + +void protocol_hitag1_decoder_start(ProtocolHitag1* protocol) { + UNUSED(protocol); + // Not applicalble, encoding & decoding is handled in lfrfid_hitag_worker... +}; + +bool protocol_hitag1_decoder_feed(ProtocolHitag1* protocol, bool level, uint32_t duration) { + UNUSED(protocol); + UNUSED(level); + UNUSED(duration); + // Not applicalble, encoding & decoding is handled in lfrfid_hitag_worker... + + bool result = false; + return result; +}; + +bool protocol_hitag1_encoder_start(ProtocolHitag1* protocol) { + UNUSED(protocol); + // Not applicalble, encoding & decoding is handled in lfrfid_hitag_worker... + + return false; +}; + +LevelDuration protocol_hitag1_encoder_yield(ProtocolHitag1* protocol) { + UNUSED(protocol); + // Not applicalble, encoding & decoding is handled in lfrfid_hitag_worker... + + bool level = 0; + uint32_t duration = 0; + return level_duration_make(level, duration); +}; + +bool protocol_hitag1_write_data(ProtocolHitag1* protocol, void* data) { + UNUSED(protocol); + UNUSED(data); + + //this protocol cannot be simply written to card --> don't do anything, just return false + + return false; +}; + +void protocol_hitag1_render_data(ProtocolHitag1* protocol, FuriString* result) { + uint8_t pages = 0; + for(uint8_t p = 0; p < HITAG1_PAGES; p++) { + pages += protocol->tagData[HITAG1_PAGES * 4 + p]; + } + furi_string_printf( + result, + "SN: %02X %02X %02X %02X\r\n" + "Pages read: %u / 64", + protocol->tagData[0], + protocol->tagData[1], + protocol->tagData[2], + protocol->tagData[3], + pages); +}; + +const ProtocolBase protocol_hitag1 = { + .name = "Hitag1", + .manufacturer = "Philips", + .data_size = HITAG1_DATA_SIZE, + .features = LFRFIDFeatureRTF, + .validate_count = 1, + .alloc = (ProtocolAlloc)protocol_hitag1_alloc, + .free = (ProtocolFree)protocol_hitag1_free, + .get_data = (ProtocolGetData)protocol_hitag1_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_hitag1_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_hitag1_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_hitag1_encoder_start, + .yield = (ProtocolEncoderYield)protocol_hitag1_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_hitag1_render_data, + .render_brief_data = (ProtocolRenderData)protocol_hitag1_render_data, + .write_data = (ProtocolWriteData)protocol_hitag1_write_data, +}; diff --git a/lib/lfrfid/protocols/protocol_hitag1.h b/lib/lfrfid/protocols/protocol_hitag1.h new file mode 100644 index 000000000..517eeb5ef --- /dev/null +++ b/lib/lfrfid/protocols/protocol_hitag1.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_hitag1; \ No newline at end of file From fc18cea875a8c8b5483bfd06c4fb0fba6131da01 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 21:22:22 +0300 Subject: [PATCH 14/40] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2f1a2a28..d15721ed9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ * Infrared: `External output` move out of debug and add power option for external modules * Extra pack: Some app fixes * FBT: Fix vscode example config for debug builds - please run `./fbt vscode_dist` again if you had issues with debug builds size +* OFW PR 2658: Hitag (by @blackvault88) (with minor fixes, hitag excluded from add manually temporarily, + fixed lfrfid favourites start when key cannot be parsed) +* OFW PR 2316: NFC V support (by @g3gg0 & @nvx) +* OFW PR 2669: nfc: Fix MFUL tearing flags read (by @GMMan) * OFW: api: added lib/nfc/protocols/nfc_util.h * OFW: fix PIN retry count reset on reboot * OFW: fbt: allow strings for fap_version field in app manifests From 95d6465dacf1b0c54bd544701c58c68c97d1e60b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 21:30:36 +0300 Subject: [PATCH 15/40] OFW PR 2666: Add fr-FR-mac key layout by FelixLgr --- CHANGELOG.md | 1 + .../resources/badusb/assets/layouts/fr-FR-mac.kl | Bin 0 -> 256 bytes 2 files changed, 1 insertion(+) create mode 100644 assets/resources/badusb/assets/layouts/fr-FR-mac.kl diff --git a/CHANGELOG.md b/CHANGELOG.md index d15721ed9..5cface529 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ * OFW PR 2658: Hitag (by @blackvault88) (with minor fixes, hitag excluded from add manually temporarily, + fixed lfrfid favourites start when key cannot be parsed) * OFW PR 2316: NFC V support (by @g3gg0 & @nvx) * OFW PR 2669: nfc: Fix MFUL tearing flags read (by @GMMan) +* OFW PR 2666: BadUSB: Add fr-FR-mac key layout (by @FelixLgr) * OFW: api: added lib/nfc/protocols/nfc_util.h * OFW: fix PIN retry count reset on reboot * OFW: fbt: allow strings for fap_version field in app manifests diff --git a/assets/resources/badusb/assets/layouts/fr-FR-mac.kl b/assets/resources/badusb/assets/layouts/fr-FR-mac.kl new file mode 100644 index 0000000000000000000000000000000000000000..0906936547cd3c8accd9632bac82cb4193a24c25 GIT binary patch literal 256 zcmaLLM{dGU007a^2*c-7jOjgKAVDOih=6?{?tg}?*<^Na;Jp*y9N*W!`r*KahgW`G zvn8kCYGsczPfNdC`{Bl|xjXkB{IulBi;9;$9}G>b+c4NP+OloOuBmr3`wpx*a_q#Z zGgmHLIyaAHEaHW;H-;qCX%J` Date: Wed, 17 May 2023 22:37:24 +0300 Subject: [PATCH 16/40] remove region_data from int --- .../storage_move_to_sd/storage_move_to_sd.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/applications/system/storage_move_to_sd/storage_move_to_sd.c b/applications/system/storage_move_to_sd/storage_move_to_sd.c index 9c91b9266..2f33eec33 100644 --- a/applications/system/storage_move_to_sd/storage_move_to_sd.c +++ b/applications/system/storage_move_to_sd/storage_move_to_sd.c @@ -5,6 +5,7 @@ #include #include #include +#include #define TAG "MoveToSd" @@ -20,6 +21,17 @@ static bool storage_move_to_sd_check_entry(const char* name, FileInfo* fileinfo, return (name && (*name != '.')); } +static void storage_move_to_sd_remove_region() { + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) return; + Storage* storage = furi_record_open(RECORD_STORAGE); + + if(storage_common_exists(storage, INT_PATH(".region_data"))) { + storage_common_remove(storage, INT_PATH(".region_data")); + } + + furi_record_close(RECORD_STORAGE); +} + bool storage_move_to_sd_perform(void) { Storage* storage = furi_record_open(RECORD_STORAGE); @@ -162,6 +174,9 @@ int32_t storage_move_to_sd_app(void* p) { FURI_LOG_I(TAG, "Nothing to move"); } + // Remove unused region file from int memory + storage_move_to_sd_remove_region(); + return 0; } From 51ba8b66b699a85fd39b5c8b804a57ca2b0c38f8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 23:05:44 +0300 Subject: [PATCH 17/40] Save a little bit (~2k) of space --- applications/main/nfc/scenes/nfc_scene_config.h | 4 ++++ applications/main/nfc/scenes/nfc_scene_debug.c | 2 ++ .../main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c | 2 ++ applications/main/nfc/scenes/nfc_scene_field.c | 2 ++ applications/main/nfc/scenes/nfc_scene_start.c | 11 ++++++++--- lib/nfc/nfc_worker.c | 11 ++++++++--- lib/nfc/protocols/emv.c | 6 ++++-- 7 files changed, 30 insertions(+), 8 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index d6edebe73..445c4436a 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -52,14 +52,18 @@ ADD_SCENE(nfc, mf_classic_update_success, MfClassicUpdateSuccess) ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard) ADD_SCENE(nfc, emv_read_success, EmvReadSuccess) ADD_SCENE(nfc, emv_menu, EmvMenu) +#if FURI_DEBUG ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence) +#endif ADD_SCENE(nfc, device_info, DeviceInfo) ADD_SCENE(nfc, delete, Delete) ADD_SCENE(nfc, delete_success, DeleteSuccess) ADD_SCENE(nfc, restore_original_confirm, RestoreOriginalConfirm) ADD_SCENE(nfc, restore_original, RestoreOriginal) +#if FURI_DEBUG ADD_SCENE(nfc, debug, Debug) ADD_SCENE(nfc, field, Field) +#endif ADD_SCENE(nfc, dict_not_found, DictNotFound) ADD_SCENE(nfc, rpc, Rpc) ADD_SCENE(nfc, exit_confirm, ExitConfirm) diff --git a/applications/main/nfc/scenes/nfc_scene_debug.c b/applications/main/nfc/scenes/nfc_scene_debug.c index ed079c2ed..5dc29c96a 100644 --- a/applications/main/nfc/scenes/nfc_scene_debug.c +++ b/applications/main/nfc/scenes/nfc_scene_debug.c @@ -1,3 +1,4 @@ +#if FURI_DEBUG #include "../nfc_i.h" enum SubmenuDebugIndex { @@ -52,3 +53,4 @@ void nfc_scene_debug_on_exit(void* context) { submenu_reset(nfc->submenu); } +#endif \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c b/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c index 358ad2ab6..bc3f0f42d 100644 --- a/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c +++ b/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c @@ -1,3 +1,4 @@ +#if FURI_DEBUG #include "../nfc_i.h" #include @@ -32,3 +33,4 @@ void nfc_scene_emulate_apdu_sequence_on_exit(void* context) { nfc_blink_stop(nfc); } +#endif \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_field.c b/applications/main/nfc/scenes/nfc_scene_field.c index e3eb6a708..f287e8796 100644 --- a/applications/main/nfc/scenes/nfc_scene_field.c +++ b/applications/main/nfc/scenes/nfc_scene_field.c @@ -1,3 +1,4 @@ +#if FURI_DEBUG #include "../nfc_i.h" void nfc_scene_field_on_enter(void* context) { @@ -31,3 +32,4 @@ void nfc_scene_field_on_exit(void* context) { notification_internal_message(nfc->notifications, &sequence_reset_blue); popup_reset(nfc->popup); } +#endif \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index a01f871ab..858d519fc 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -8,7 +8,9 @@ enum SubmenuIndex { SubmenuIndexSaved, SubmenuIndexExtraAction, SubmenuIndexAddManually, +#if FURI_DEBUG SubmenuIndexDebug, +#endif }; void nfc_scene_start_submenu_callback(void* context, uint32_t index) { @@ -29,12 +31,12 @@ void nfc_scene_start_on_enter(void* context) { submenu, "Extra Actions", SubmenuIndexExtraAction, nfc_scene_start_submenu_callback, nfc); submenu_add_item( submenu, "Add Manually", SubmenuIndexAddManually, nfc_scene_start_submenu_callback, nfc); - +#if FURI_DEBUG if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { submenu_add_item( submenu, "Debug", SubmenuIndexDebug, nfc_scene_start_submenu_callback, nfc); } - +#endif submenu_set_selected_item( submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneStart)); @@ -82,11 +84,14 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { nfc->scene_manager, NfcSceneStart, SubmenuIndexAddManually); scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType); consumed = true; - } else if(event.event == SubmenuIndexDebug) { + } +#if FURI_DEBUG + else if(event.event == SubmenuIndexDebug) { scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexDebug); scene_manager_next_scene(nfc->scene_manager, NfcSceneDebug); consumed = true; } +#endif } return consumed; } diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 197b6f58a..8f7a1b5e4 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -95,9 +95,13 @@ int32_t nfc_worker_task(void* context) { } } else if(nfc_worker->state == NfcWorkerStateUidEmulate) { nfc_worker_emulate_uid(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateEmulateApdu) { + } +#if FURI_DEBUG + else if(nfc_worker->state == NfcWorkerStateEmulateApdu) { nfc_worker_emulate_apdu(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { + } +#endif + else if(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { nfc_worker_emulate_mf_ultralight(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { nfc_worker_emulate_mf_classic(nfc_worker); @@ -741,7 +745,7 @@ void nfc_worker_emulate_uid(NfcWorker* nfc_worker) { } } } - +#if FURI_DEBUG void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { FuriHalNfcTxRxContext tx_rx = {}; FuriHalNfcDevData params = { @@ -774,6 +778,7 @@ void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { reader_analyzer_stop(nfc_worker->reader_analyzer); } } +#endif void nfc_worker_mf_ultralight_auth_received_callback(MfUltralightAuth auth, void* context) { furi_assert(context); diff --git a/lib/nfc/protocols/emv.c b/lib/nfc/protocols/emv.c index 4c4ac856b..8cc5fc44e 100644 --- a/lib/nfc/protocols/emv.c +++ b/lib/nfc/protocols/emv.c @@ -44,7 +44,7 @@ const PDOLValue* const pdol_values[] = { &pdol_transaction_cert, &pdol_unpredict_number, }; - +#if FURI_DEBUG static const uint8_t select_ppse_ans[] = {0x6F, 0x29, 0x84, 0x0E, 0x32, 0x50, 0x41, 0x59, 0x2E, 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31, 0xA5, 0x17, 0xBF, 0x0C, 0x14, 0x61, 0x12, 0x4F, 0x07, @@ -61,7 +61,7 @@ static const uint8_t pdol_ans[] = {0x77, 0x40, 0x82, 0x02, 0x20, 0x00, 0x57, 0x1 0x00, 0x9F, 0x26, 0x08, 0x7A, 0x65, 0x7F, 0xD3, 0x52, 0x96, 0xC9, 0x85, 0x9F, 0x27, 0x01, 0x00, 0x9F, 0x36, 0x02, 0x06, 0x0C, 0x9F, 0x6C, 0x02, 0x10, 0x00, 0x90, 0x00}; - +#endif static void emv_trace(FuriHalNfcTxRxContext* tx_rx, const char* message) { if(furi_log_get_level() == FuriLogLevelTrace) { FURI_LOG_T(TAG, "%s", message); @@ -409,6 +409,7 @@ bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) { return card_num_read; } +#if FURI_DEBUG bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) { furi_assert(tx_rx); bool emulation_complete = false; @@ -442,3 +443,4 @@ bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) { return emulation_complete; } +#endif \ No newline at end of file From d539b948a00a9d846fe1f828dc9b9738c3fa0ebe Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 23:06:41 +0300 Subject: [PATCH 18/40] Revert hitag for now, need to cleanup more space --- applications/main/lfrfid/lfrfid_i.h | 2 - .../scenes/lfrfid_scene_extra_actions.c | 12 - .../main/lfrfid/scenes/lfrfid_scene_read.c | 18 - .../lfrfid/scenes/lfrfid_scene_save_type.c | 3 - .../lfrfid/scenes/lfrfid_scene_saved_info.c | 12 +- .../main/lfrfid/views/lfrfid_view_read.c | 73 +- .../main/lfrfid/views/lfrfid_view_read.h | 11 +- .../services/gui/modules/byte_input.c | 14 +- .../services/gui/modules/byte_input.h | 2 +- firmware/targets/f7/api_symbols.csv | 2 +- lib/lfrfid/lfrfid_dict_file.c | 82 +- lib/lfrfid/lfrfid_hitag_worker.c | 2370 ----------------- lib/lfrfid/lfrfid_hitag_worker.h | 86 - lib/lfrfid/lfrfid_worker.h | 3 - lib/lfrfid/lfrfid_worker_i.h | 1 - lib/lfrfid/lfrfid_worker_modes.c | 120 +- lib/lfrfid/protocols/lfrfid_protocols.c | 2 - lib/lfrfid/protocols/lfrfid_protocols.h | 2 - lib/lfrfid/protocols/protocol_hitag1.c | 104 - lib/lfrfid/protocols/protocol_hitag1.h | 4 - 20 files changed, 41 insertions(+), 2882 deletions(-) delete mode 100644 lib/lfrfid/lfrfid_hitag_worker.c delete mode 100644 lib/lfrfid/lfrfid_hitag_worker.h delete mode 100644 lib/lfrfid/protocols/protocol_hitag1.c delete mode 100644 lib/lfrfid/protocols/protocol_hitag1.h diff --git a/applications/main/lfrfid/lfrfid_i.h b/applications/main/lfrfid/lfrfid_i.h index a883d1926..86929a14a 100644 --- a/applications/main/lfrfid/lfrfid_i.h +++ b/applications/main/lfrfid/lfrfid_i.h @@ -56,8 +56,6 @@ enum LfRfidCustomEvent { LfRfidEventReadSenseCardEnd, LfRfidEventReadStartASK, LfRfidEventReadStartPSK, - LfRfidEventReadStartRTF, - LfRfidEventReadSenseHitag, //TODO combine with sensecardstart? LfRfidEventReadDone, LfRfidEventReadOverrun, LfRfidEventReadError, diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c index 53bf32bbd..d2bf20680 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c @@ -4,7 +4,6 @@ typedef enum { SubmenuIndexASK, SubmenuIndexPSK, - SubmenuIndexHitag, SubmenuIndexClearT5577, SubmenuIndexRAW, SubmenuIndexRAWEmulate, @@ -32,12 +31,6 @@ void lfrfid_scene_extra_actions_on_enter(void* context) { SubmenuIndexPSK, lfrfid_scene_extra_actions_submenu_callback, app); - submenu_add_item( - submenu, - "Read RTF (Reader Talks First)", - SubmenuIndexHitag, - lfrfid_scene_extra_actions_submenu_callback, - app); submenu_add_item( submenu, "Clear T5577 Password", @@ -86,11 +79,6 @@ bool lfrfid_scene_extra_actions_on_event(void* context, SceneManagerEvent event) scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); DOLPHIN_DEED(DolphinDeedRfidRead); consumed = true; - } else if(event.event == SubmenuIndexHitag) { - app->read_type = LFRFIDWorkerReadTypeRTFOnly; - scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); - DOLPHIN_DEED(DolphinDeedRfidRead); - consumed = true; } else if(event.event == SubmenuIndexClearT5577) { scene_manager_next_scene(app->scene_manager, LfRfidSceneClearT5577Confirm); consumed = true; diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read.c b/applications/main/lfrfid/scenes/lfrfid_scene_read.c index e9168c818..5f1959728 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read.c @@ -36,10 +36,6 @@ static void event = LfRfidEventReadStartASK; } else if(result == LFRFIDWorkerReadStartPSK) { event = LfRfidEventReadStartPSK; - } else if(result == LFRFIDWorkerReadStartRTF) { - event = LfRfidEventReadStartRTF; - } else if(result == LFRFIDWorkerReadSenseHitag) { //TODO combine with sensecardstart? - event = LfRfidEventReadSenseHitag; } else { return; } @@ -54,9 +50,6 @@ void lfrfid_scene_read_on_enter(void* context) { lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadPskOnly); } else if(app->read_type == LFRFIDWorkerReadTypeASKOnly) { lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadAskOnly); - } else if(app->read_type == LFRFIDWorkerReadTypeRTFOnly) { - lfrfid_view_read_set_read_state(app->read_view, LfRfidReadScanning); - lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadRtfOnly); } lfrfid_worker_start_thread(app->lfworker); @@ -100,17 +93,6 @@ bool lfrfid_scene_read_on_event(void* context, SceneManagerEvent event) { lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadAsk); } consumed = true; - } else if(event.event == LfRfidEventReadStartRTF) { - if(app->read_type == LFRFIDWorkerReadTypeAuto) { - lfrfid_view_read_set_read_state(app->read_view, LfRfidReadScanning); - lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadHitag); - } - consumed = true; - } else if(event.event == LfRfidEventReadSenseHitag) { //TODO combine with sensecardstart? - if(app->read_type == LFRFIDWorkerReadTypeAuto || - app->read_type == LFRFIDWorkerReadTypeRTFOnly) { - lfrfid_view_read_set_read_state(app->read_view, LfRfidReadTagDetected); - } } } diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c index f7bd6c6e5..eb73b1123 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c @@ -17,9 +17,6 @@ void lfrfid_scene_save_type_on_enter(void* context) { SaveTypeCtx* state = malloc(sizeof(SaveTypeCtx)); FuriString* protocol_string = furi_string_alloc(); for(uint8_t i = 0; i < LFRFIDProtocolMax; i++) { - if(i == LFRFIDProtocolHitag1) { - continue; - } if((strcmp( protocol_dict_get_manufacturer(app->dict, i), protocol_dict_get_name(app->dict, i)) != 0) && diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c b/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c index 812450314..c901629c3 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c @@ -17,15 +17,11 @@ void lfrfid_scene_saved_info_on_enter(void* context) { uint8_t* data = (uint8_t*)malloc(size); protocol_dict_get_data(app->dict, app->protocol_id, data, size); for(uint8_t i = 0; i < size; i++) { - if(i >= 18) { - furi_string_cat_printf(tmp_string, ".."); - break; - } else { - if(i != 0) { - furi_string_cat_printf(tmp_string, ":"); - } - furi_string_cat_printf(tmp_string, "%02X", data[i]); + if(i != 0) { + furi_string_cat_printf(tmp_string, ":"); } + + furi_string_cat_printf(tmp_string, "%02X", data[i]); } free(data); diff --git a/applications/main/lfrfid/views/lfrfid_view_read.c b/applications/main/lfrfid/views/lfrfid_view_read.c index c0dda4178..094afb617 100644 --- a/applications/main/lfrfid/views/lfrfid_view_read.c +++ b/applications/main/lfrfid/views/lfrfid_view_read.c @@ -11,7 +11,6 @@ struct LfRfidReadView { typedef struct { IconAnimation* icon; LfRfidReadViewMode read_mode; - LfRfidReadViewState read_state; } LfRfidReadViewModel; static void lfrfid_view_read_draw_callback(Canvas* canvas, void* _model) { @@ -23,69 +22,36 @@ static void lfrfid_view_read_draw_callback(Canvas* canvas, void* _model) { canvas_set_font(canvas, FontPrimary); if(model->read_mode == LfRfidReadAsk) { - canvas_draw_str(canvas, 70, 8, "Reading 1/3"); + canvas_draw_str(canvas, 70, 16, "Reading 1/2"); - canvas_draw_str(canvas, 77, 20, "ASK"); - canvas_draw_icon(canvas, 70, 13, &I_ButtonRight_4x7); - canvas_draw_icon_animation(canvas, 112, 12, model->icon); + canvas_draw_str(canvas, 77, 29, "ASK"); + canvas_draw_icon(canvas, 70, 22, &I_ButtonRight_4x7); + canvas_draw_icon_animation(canvas, 102, 21, model->icon); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 77, 33, "PSK"); - canvas_draw_str(canvas, 77, 46, "RTF"); + canvas_draw_str(canvas, 77, 43, "PSK"); } else if(model->read_mode == LfRfidReadPsk) { - canvas_draw_str(canvas, 70, 8, "Reading 2/3"); + canvas_draw_str(canvas, 70, 16, "Reading 2/2"); - canvas_draw_str(canvas, 77, 33, "PSK"); - canvas_draw_icon(canvas, 70, 26, &I_ButtonRight_4x7); - canvas_draw_icon_animation(canvas, 112, 25, model->icon); + canvas_draw_str(canvas, 77, 43, "PSK"); + canvas_draw_icon(canvas, 70, 36, &I_ButtonRight_4x7); + canvas_draw_icon_animation(canvas, 102, 35, model->icon); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 77, 20, "ASK"); - canvas_draw_str(canvas, 77, 46, "RTF"); - } else if(model->read_mode == LfRfidReadHitag) { - if(model->read_state == LfRfidReadScanning) { - canvas_draw_str(canvas, 70, 8, "Reading 3/3"); - - canvas_draw_str(canvas, 77, 46, "RTF"); - canvas_draw_icon(canvas, 70, 39, &I_ButtonRight_4x7); - canvas_draw_icon_animation(canvas, 112, 38, model->icon); - - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 77, 20, "ASK"); - canvas_draw_str(canvas, 77, 33, "PSK"); - } else if(model->read_state == LfRfidReadTagDetected) { //TODO switch to other scene? - canvas_draw_str(canvas, 65, 8, "Hitag1 found"); - - canvas_set_font(canvas, FontSecondary); - //canvas_draw_str(canvas, 70, 20, "## ## ## ##"); //TODO get tag SN from hitag worker - canvas_draw_str(canvas, 70, 33, "Reading data"); - //canvas_draw_str(canvas, 70, 46, "Page: X/64"); //TODO get current page index from hitag worker - } - } else if(model->read_mode == LfRfidReadAskOnly) { + canvas_draw_str(canvas, 77, 29, "ASK"); + } else { canvas_draw_str(canvas, 72, 16, "Reading"); - canvas_draw_str(canvas, 77, 35, "ASK"); - canvas_draw_icon_animation(canvas, 112, 27, model->icon); - } else if(model->read_mode == LfRfidReadPskOnly) { - canvas_draw_str(canvas, 72, 16, "Reading"); - canvas_draw_str(canvas, 77, 35, "PSK"); - canvas_draw_icon_animation(canvas, 112, 27, model->icon); - } else if(model->read_mode == LfRfidReadRtfOnly) { - if(model->read_state == LfRfidReadScanning) { - canvas_draw_str(canvas, 72, 16, "Reading"); - canvas_draw_str(canvas, 77, 35, "RTF"); - canvas_draw_icon_animation(canvas, 112, 27, model->icon); - } else if(model->read_state == LfRfidReadTagDetected) { //TODO switch to other scene? - canvas_draw_str(canvas, 65, 8, "Hitag1 found"); - canvas_set_font(canvas, FontSecondary); - //canvas_draw_str(canvas, 70, 20, "## ## ## ##"); //TODO get tag SN from hitag worker - canvas_draw_str(canvas, 70, 33, "Reading data"); - //canvas_draw_str(canvas, 70, 46, "Page: X/64"); //TODO get current page index from hitag worker + if(model->read_mode == LfRfidReadAskOnly) { + canvas_draw_str(canvas, 77, 35, "ASK"); + } else { + canvas_draw_str(canvas, 77, 35, "PSK"); } + canvas_draw_icon_animation(canvas, 102, 27, model->icon); } canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 61, 60, "Don't move card"); + canvas_draw_str(canvas, 61, 56, "Don't move card"); } void lfrfid_view_read_enter(void* context) { @@ -145,8 +111,3 @@ void lfrfid_view_read_set_read_mode(LfRfidReadView* read_view, LfRfidReadViewMod }, true); } - -void lfrfid_view_read_set_read_state(LfRfidReadView* read_view, LfRfidReadViewState state) { - with_view_model( - read_view->view, LfRfidReadViewModel * model, { model->read_state = state; }, true); -} \ No newline at end of file diff --git a/applications/main/lfrfid/views/lfrfid_view_read.h b/applications/main/lfrfid/views/lfrfid_view_read.h index 7d6d8ead4..55bb1f230 100644 --- a/applications/main/lfrfid/views/lfrfid_view_read.h +++ b/applications/main/lfrfid/views/lfrfid_view_read.h @@ -4,17 +4,10 @@ typedef enum { LfRfidReadAsk, LfRfidReadPsk, - LfRfidReadHitag, LfRfidReadAskOnly, - LfRfidReadPskOnly, - LfRfidReadRtfOnly, + LfRfidReadPskOnly } LfRfidReadViewMode; -typedef enum { - LfRfidReadScanning, - LfRfidReadTagDetected, -} LfRfidReadViewState; - typedef struct LfRfidReadView LfRfidReadView; LfRfidReadView* lfrfid_view_read_alloc(); @@ -24,5 +17,3 @@ void lfrfid_view_read_free(LfRfidReadView* read_view); View* lfrfid_view_read_get_view(LfRfidReadView* read_view); void lfrfid_view_read_set_read_mode(LfRfidReadView* read_view, LfRfidReadViewMode mode); - -void lfrfid_view_read_set_read_state(LfRfidReadView* read_view, LfRfidReadViewState state); \ No newline at end of file diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index f1edf9de8..b2d21f7ae 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -17,17 +17,17 @@ typedef struct { typedef struct { const char* header; uint8_t* bytes; - uint16_t bytes_count; + uint8_t bytes_count; ByteInputCallback input_callback; ByteChangedCallback changed_callback; void* callback_context; bool selected_high_nibble; - uint16_t selected_byte; + uint8_t selected_byte; int8_t selected_row; // row -1 - input, row 0 & 1 - keyboard uint8_t selected_column; - uint16_t first_visible_byte; + uint8_t first_visible_byte; } ByteInputModel; static const uint8_t keyboard_origin_x = 7; @@ -164,7 +164,7 @@ static void byte_input_draw_input(Canvas* canvas, ByteInputModel* model) { canvas_draw_icon(canvas, 2, 19, &I_ButtonLeftSmall_3x5); canvas_draw_icon(canvas, 123, 19, &I_ButtonRightSmall_3x5); - for(uint16_t i = model->first_visible_byte; + for(uint8_t i = model->first_visible_byte; i < model->first_visible_byte + MIN(model->bytes_count, max_drawable_bytes); i++) { uint8_t byte_position = i - model->first_visible_byte; @@ -253,7 +253,7 @@ static void byte_input_draw_input_selected(Canvas* canvas, ByteInputModel* model canvas_draw_icon(canvas, 2, 19, &I_ButtonLeftSmall_3x5); canvas_draw_icon(canvas, 122, 19, &I_ButtonRightSmall_3x5); - for(uint16_t i = model->first_visible_byte; + for(uint8_t i = model->first_visible_byte; i < model->first_visible_byte + MIN(model->bytes_count, max_drawable_bytes); i++) { uint8_t byte_position = i - model->first_visible_byte; @@ -305,7 +305,7 @@ static void byte_input_draw_input_selected(Canvas* canvas, ByteInputModel* model * @param value char value * @param high_nibble set high nibble */ -static void byte_input_set_nibble(uint8_t* data, uint16_t position, char value, bool high_nibble) { +static void byte_input_set_nibble(uint8_t* data, uint8_t position, char value, bool high_nibble) { switch(value) { case '0': case '1': @@ -750,7 +750,7 @@ void byte_input_set_result_callback( ByteChangedCallback changed_callback, void* callback_context, uint8_t* bytes, - uint16_t bytes_count) { + uint8_t bytes_count) { with_view_model( byte_input->view, ByteInputModel * model, diff --git a/applications/services/gui/modules/byte_input.h b/applications/services/gui/modules/byte_input.h index b8a4d4455..42c4b5d65 100644 --- a/applications/services/gui/modules/byte_input.h +++ b/applications/services/gui/modules/byte_input.h @@ -55,7 +55,7 @@ void byte_input_set_result_callback( ByteChangedCallback changed_callback, void* callback_context, uint8_t* bytes, - uint16_t bytes_count); + uint8_t bytes_count); /** Set byte input header text * diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 4ea51493c..84d0beda8 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -626,7 +626,7 @@ Function,+,byte_input_alloc,ByteInput*, Function,+,byte_input_free,void,ByteInput* Function,+,byte_input_get_view,View*,ByteInput* Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" -Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint16_t" +Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" Function,-,bzero,void,"void*, size_t" Function,-,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* diff --git a/lib/lfrfid/lfrfid_dict_file.c b/lib/lfrfid/lfrfid_dict_file.c index f8c3d5ef7..7ae84f8b6 100644 --- a/lib/lfrfid/lfrfid_dict_file.c +++ b/lib/lfrfid/lfrfid_dict_file.c @@ -5,42 +5,6 @@ #define LFRFID_DICT_FILETYPE "Flipper RFID key" -bool lfrfid_dict_file_save_hitag1_data(FlipperFormat* file, uint8_t* data) { - FuriString* string = furi_string_alloc(); - bool result = false; - uint8_t pageSize = 4; - - do { - //write shortened data (tag ID) - if(!flipper_format_write_hex(file, "Data", data, pageSize)) break; - - if(!flipper_format_write_comment_cstr(file, "Hitag1 specific data")) break; - - //write pages - for(uint8_t p = 0; p < 64; p++) { - furi_string_printf(string, "Page %2u", p); - if(data[64 * pageSize + p]) { - //write page data - if(!flipper_format_write_hex( - file, furi_string_get_cstr(string), data + p * pageSize, pageSize)) - break; - } else { - //write ?? ?? ?? ?? - if(!flipper_format_write_string_cstr( - file, furi_string_get_cstr(string), "?? ?? ?? ??")) - break; - } - if(p == 64 - 1) { - result = true; - } - } - } while(false); - - furi_string_free(string); - - return result; -} - bool lfrfid_dict_file_save(ProtocolDict* dict, ProtocolId protocol, const char* filename) { furi_check(protocol != PROTOCOL_NO); Storage* storage = furi_record_open(RECORD_STORAGE); @@ -62,12 +26,8 @@ bool lfrfid_dict_file_save(ProtocolDict* dict, ProtocolId protocol, const char* // TODO: write comment about protocol sizes into file protocol_dict_get_data(dict, protocol, data, data_size); - if(protocol == LFRFIDProtocolHitag1) { - if(!lfrfid_dict_file_save_hitag1_data(file, data)) break; - } else { - if(!flipper_format_write_hex(file, "Data", data, data_size)) break; - } + if(!flipper_format_write_hex(file, "Data", data, data_size)) break; result = true; } while(false); @@ -178,41 +138,6 @@ static ProtocolId lfrfid_dict_protocol_fallback( return result; } -bool lfrfid_dict_file_load_hitag1_data(FlipperFormat* file, uint8_t* data) { - FuriString* string = furi_string_alloc(); - bool result = false; - uint8_t tagID[4]; - uint8_t pageSize = 4; - - do { - //read shortened data (tag ID) - if(!flipper_format_read_hex(file, "Data", tagID, 4)) break; - - //read pages - for(uint8_t p = 0; p < 64; p++) { - furi_string_printf(string, "Page %2u", p); - if(flipper_format_read_hex( - file, furi_string_get_cstr(string), data + p * pageSize, pageSize)) { - data[64 * pageSize + p] = 1; - } else { - data[64 * pageSize + p] = 0; - } - } - - //check data consistency - if(memcmp(tagID, data, pageSize) != 0) break; - - //check if tag ID & config page are succesfully read - if(data[64 * pageSize + 0] && data[64 * pageSize + 1]) { - result = true; - } - } while(false); - - furi_string_free(string); - - return result; -} - ProtocolId lfrfid_dict_file_load(ProtocolDict* dict, const char* filename) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* file = flipper_format_file_alloc(storage); @@ -238,11 +163,6 @@ ProtocolId lfrfid_dict_file_load(ProtocolDict* dict, const char* filename) { if(protocol == PROTOCOL_NO) { protocol = lfrfid_dict_protocol_fallback(dict, furi_string_get_cstr(str_result), file); if(protocol == PROTOCOL_NO) break; - } else if(protocol == LFRFIDProtocolHitag1) { - // Hitag1 data - size_t data_size = protocol_dict_get_data_size(dict, protocol); - if(!lfrfid_dict_file_load_hitag1_data(file, data)) break; - protocol_dict_set_data(dict, protocol, data, data_size); } else { // data size_t data_size = protocol_dict_get_data_size(dict, protocol); diff --git a/lib/lfrfid/lfrfid_hitag_worker.c b/lib/lfrfid/lfrfid_hitag_worker.c deleted file mode 100644 index 02cd14ca0..000000000 --- a/lib/lfrfid/lfrfid_hitag_worker.c +++ /dev/null @@ -1,2370 +0,0 @@ -#include "lfrfid_hitag_worker.h" - -#define DMA_BUFFER_SIZE \ - 4000 //I need to keep this big enough? to have enough time inbetween HT & TC events perhaps? -#define HITAG_CMD_BUFFER_SIZE 100 //this was originally 1000, but can be lowered - -#define READ_BUFFER_SIZE 4000 -#define READ_BUFFER_COUNT 4 - -#define EMULATE_BUFFER_SIZE 50 -#define EMULATE_BUFFER_COUNT 50 - -//bitlength/ID for read cmd structure -#define HITAG_STOP 102 -#define HITAG_ON 101 -#define HITAG_OFF 100 -#define HITAG_SET 5 -#define HITAG_SELECT 5 -#define HITAG_CMD 4 -#define HITAG_BYTE 8 -#define HITAG_CRC 8 - -//carrier periods for read cmd structure -#define HITAG_LOW 7 -#define HITAG_STOP_HIGH 40 -#define HITAG_1_HIGH 21 -#define HITAG_0_HIGH 15 - -//bitlength for emulate reply cmd structure -#define HITAG_PAGE 32 -#define HITAG_STARTBIT 1 -#define HITAG_STARTBIT3 3 -#define HITAG_STARTBIT6 6 -#define HITAG_ACK 3 -#define HITAG_ACK_ADV 8 - -#define HITAG_BASEPERIOD 8 - -//carrier periods for read reply decoding -#define HITAG_DURATION_S 256 -#define HITAG_DURATION_M 384 -#define HITAG_DURATION_L 512 -#define HITAG_DURATION_ERROR_MARGIN 0.15 - -#define HITAG_BITPERIODS_AC 64 -#define HITAG_BITPERIODS_MC 32 - -//MISC -#define READ 1 -#define WRITE 2 - -#define CRC_PRESET 0xFF -#define CRC_POLYNOM 0x1D - -#define TAG "LFRFIDHitagWorker" - -// TIMER definitions -#define CARRIER_OUT_TIMER TIM1 -//#define CARRIER_OUT_TIMER_IRQ FuriHalInterruptIdTIM1 //TODO am i using this? -#define CARRIER_OUT_TIMER_CHANNEL LL_TIM_CHANNEL_CH1 // or LL_TIM_CHANNEL_CH1N - -#define CARRIER_IN_TIMER TIM2 -#define CARRIER_IN_TIMER_IND_CH LL_TIM_CHANNEL_CH2 //no longer used via ETR setup -#define CARRIER_IN_TIMER_DIR_CH LL_TIM_CHANNEL_CH1 //no longer used via ETR setup - -#define CARRIER_IN_REFERENCE_TIMER TIM1 - -#define PULL_OUT_TIMER TIM2 -#define PULL_OUT_TIMER_CHANNEL LL_TIM_CHANNEL_CH3 - -// DMA Channels definition -#define PULL_OUT_DMA DMA2 -#define PULL_OUT_DMA_CH1 LL_DMA_CHANNEL_1 -#define PULL_OUT_DMA_CH2 LL_DMA_CHANNEL_2 -#define PULL_OUT_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1 -#define PULL_OUT_DMA_CH1_DEF PULL_OUT_DMA, PULL_OUT_DMA_CH1 -#define PULL_OUT_DMA_CH2_DEF PULL_OUT_DMA, PULL_OUT_DMA_CH2 - -typedef enum { - HitagProtocolAntiCollision, - HitagProtocolManchesterCoding, -} LfRfidHitagProtocol; - -typedef enum { - LFRFIDHitagWorkerSignalHalfTransfer, - LFRFIDHitagWorkerSignalTransferComplete, - LFRFIDHitagWorkerSignalStop, -} LFRFIDHitagWorkerSignal; - -typedef enum { - HitagStateIdle, - HitagStateSelected, - HitagStateQuiet, -} LfRfidHitagState; - -typedef enum { - HitagModeBasic, - HitagModeAdvanced, -} LfRfidHitagMode; - -typedef struct { - uint32_t timer_buffer_arr[DMA_BUFFER_SIZE]; - uint32_t timer_buffer_ccr[DMA_BUFFER_SIZE]; -} TimerDMAData; - -typedef struct { - uint32_t CMDvalue[HITAG_CMD_BUFFER_SIZE]; //this can likely be a uint16_t instead of uint32_t - uint8_t CMDtype[HITAG_CMD_BUFFER_SIZE]; - uint16_t CMDlength; - uint16_t CMDposition; - uint16_t CMDsubposition; - bool CMDloop; - bool CMDactive; - uint32_t CMDcount; -} LfRfidHitagCMDData; - -typedef struct { - uint32_t replyvalue[HITAG_CMD_BUFFER_SIZE]; //this can likely be a uint8_t instead of uint32_t - uint8_t replytype[HITAG_CMD_BUFFER_SIZE]; - uint16_t replylength; -} LfRfidHitagReplyData; - -typedef struct { - BufferStream* stream; - VarintPair* pair; - uint32_t capCounter; - uint32_t prevTIMval; -} LfRfidHitagCaptureData; - -typedef struct { - uint8_t pageData[HITAG_PAGES * HITAG_PAGEBYTES]; - uint8_t pageKnown[HITAG_PAGES]; - uint8_t tagData[HITAG_PAGES * HITAG_PAGEBYTES + HITAG_PAGES]; - uint8_t pageRW[HITAG_PAGES]; - uint8_t pagePublic[HITAG_PAGES]; -} LFRFIDHitag; - -// main worker -struct LFRFIDHitagWorker { - FuriThread* thread; - FuriEventFlag* events; - uint8_t DMAeventCount; - - uint8_t carrierPrescaler; - - LFRFIDHitag* tag; - - LFRFIDHitagStatus workerstatus; - - ProtocolDict* dict; -}; - -//------------------------------------------------------------------ forward function definitions ------------------------------------------------------------------ - -static int32_t lfrfid_hitag_worker_emulate_thread(void* thread_context); -static int32_t lfrfid_hitag_worker_read_thread(void* thread_context); - -static void lfrfid_hitag_worker_carrier_in_IC_mode(void* capture_context); - -//------------------------------------------------------------------ shared functions ------------------------------------------------------------------ - -LFRFIDHitagWorker* lfrfid_hitag_worker_alloc(ProtocolDict* dict) { - furi_assert(dict); - - LFRFIDHitagWorker* worker = malloc(sizeof(LFRFIDHitagWorker)); - worker->dict = dict; - - worker->thread = furi_thread_alloc_ex("LFRFIDHitagWorker", 2048, NULL, worker); - - worker->events = furi_event_flag_alloc(NULL); - worker->DMAeventCount = 0; - - worker->carrierPrescaler = 2; - - worker->workerstatus = LFRFIDHitagStatusScanning; - - return worker; -} - -void lfrfid_hitag_worker_free(LFRFIDHitagWorker* worker) { - furi_thread_free(worker->thread); - furi_event_flag_free(worker->events); - free(worker); -} - -LFRFIDHitagStatus lfrfid_hitag_worker_get_status(LFRFIDHitagWorker* worker) { - return worker->workerstatus; -} - -void lfrfid_hitag_worker_start(LFRFIDHitagWorker* worker, LFRFIDHitagWorkerSetting setting) { - if(furi_thread_get_state(worker->thread) == FuriThreadStateStopped) { - switch(setting) { - case(LFRFIDHitagWorkerSettingRead): - DOLPHIN_DEED(DolphinDeedRfidRead); - furi_thread_set_callback(worker->thread, lfrfid_hitag_worker_read_thread); - break; - case(LFRFIDHitagWorkerSettingEmulate): - DOLPHIN_DEED(DolphinDeedRfidEmulate); - furi_thread_set_callback(worker->thread, lfrfid_hitag_worker_emulate_thread); - break; - default: //safety meassure, not to start a thread without a callback function - furi_thread_set_callback(worker->thread, lfrfid_hitag_worker_read_thread); - break; - } - furi_thread_start(worker->thread); - } -} - -void lfrfid_hitag_worker_stop(LFRFIDHitagWorker* worker) { - furi_event_flag_set(worker->events, 1 << LFRFIDHitagWorkerSignalStop); - furi_thread_join(worker->thread); -} - -//------------------------------------------------------------------ READ TAG: capture input interrupt functions ------------------------------------------------------------------ - -static void lfrfid_hitag_worker_capture_in_cc_isr(bool level, uint32_t duration, void* context) { - LfRfidHitagCaptureData* capData = context; - - //check if there is a new pair available: pulse + period - bool need_to_send = varint_pair_pack(capData->pair, level, duration); - - //if so put it on buffer stream so it can be processed outside this interrupt routine - if(need_to_send) { - buffer_stream_send_from_isr( - capData->stream, - varint_pair_get_data(capData->pair), - varint_pair_get_size(capData->pair)); - varint_pair_reset(capData->pair); - } -} - -//------------------------------------------------------------------ READ TAG: carrier out TIMER, DMA & interrupts functions ------------------------------------------------------------------ - -static void lfrfid_hitag_worker_carrier_out_dma_isr(void* dma_context) { - LFRFIDHitagWorker* worker = (LFRFIDHitagWorker*)dma_context; - worker->DMAeventCount++; - - if(LL_DMA_IsActiveFlag_HT1(DMA1)) { - LL_DMA_ClearFlag_HT1(DMA1); - //halfway through DMA buffer --> first half can be repopulated - //just signal that it can be repopulated, not execute the repopulating itself, since this is an interrupt, holding up all other stuff - furi_event_flag_set(worker->events, 1 << LFRFIDHitagWorkerSignalHalfTransfer); - } - - if(LL_DMA_IsActiveFlag_TC1(DMA1)) { - LL_DMA_ClearFlag_TC1(DMA1); - //fully through DMA buffer --> second half can be repopulated - //just signal that it can be repopulated, not execute the repopulating itself, since this is an interrupt, holding up all other stuff - furi_event_flag_set(worker->events, 1 << LFRFIDHitagWorkerSignalTransferComplete); - } -} - -void lfrfid_hitag_worker_carrier_out_start( - uint32_t* duration, - uint32_t* pulse, - size_t length, - void* context) { - // configure timer - FURI_CRITICAL_ENTER(); - LL_TIM_DeInit(CARRIER_OUT_TIMER); - FURI_CRITICAL_EXIT(); - - LL_TIM_InitTypeDef TIM_InitStruct = {0}; - TIM_InitStruct.Prescaler = SystemCoreClock / (125000 * HITAG_BASEPERIOD) - - 1; // sets the basis TIMER frequency to 8*freq (1MHz) for the timer - TIM_InitStruct.Autoreload = - HITAG_BASEPERIOD - - 1; //initial PWM period =125kHz (ARR will be handled via DMA further on) - LL_TIM_Init(CARRIER_OUT_TIMER, &TIM_InitStruct); - LL_TIM_DisableARRPreload(CARRIER_OUT_TIMER); - - LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; - TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; - TIM_OC_InitStruct.OCNState = LL_TIM_OCSTATE_ENABLE; - TIM_OC_InitStruct.CompareValue = - HITAG_BASEPERIOD / - 2; //initial pulse duration of half period ((CCR will be handled via DMA further on) - LL_TIM_OC_Init(CARRIER_OUT_TIMER, CARRIER_OUT_TIMER_CHANNEL, &TIM_OC_InitStruct); - - LL_TIM_OC_SetPolarity(CARRIER_OUT_TIMER, CARRIER_OUT_TIMER_CHANNEL, LL_TIM_OCPOLARITY_HIGH); - LL_TIM_EnableDMAReq_UPDATE(CARRIER_OUT_TIMER); - - // configure DMA "mem -> ARR" channel - LL_DMA_InitTypeDef dma_config = {0}; - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (CARRIER_OUT_TIMER->ARR); - dma_config.MemoryOrM2MDstAddress = (uint32_t)duration; - 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_WORD; - dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - dma_config.NbData = length; - dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP; - dma_config.Priority = LL_DMA_MODE_NORMAL; - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); - - // configure DMA "mem -> CCR1" channel - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (CARRIER_OUT_TIMER->CCR1); - dma_config.MemoryOrM2MDstAddress = (uint32_t)pulse; - 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_WORD; - dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - dma_config.NbData = length; - dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP; - dma_config.Priority = LL_DMA_MODE_NORMAL; - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); - - // attach interrupt to one of DMA channels - furi_hal_interrupt_set_isr( - FuriHalInterruptIdDma1Ch1, lfrfid_hitag_worker_carrier_out_dma_isr, context); - LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_1); - - // start - LL_TIM_EnableAllOutputs(CARRIER_OUT_TIMER); - - LL_TIM_SetCounter(CARRIER_OUT_TIMER, 0); - LL_TIM_EnableCounter(CARRIER_OUT_TIMER); -} - -void lfrfid_hitag_worker_carrier_out_stop() { - LL_TIM_DisableCounter(CARRIER_OUT_TIMER); - LL_TIM_DisableAllOutputs(CARRIER_OUT_TIMER); - - furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); - LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_1); - - FURI_CRITICAL_ENTER(); - - LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_2); - LL_TIM_DeInit(CARRIER_OUT_TIMER); - - FURI_CRITICAL_EXIT(); -} - -//------------------------------------------------------------------ EMULATE TAG: pull out TIMER, DMA & interrupts functions ------------------------------------------------------------------ - -static void lfrfid_hitag_worker_pull_out_dma_stop(void* capture_context) { - //reconfigure pull_out pin to fixed low state - //via forced OC INACTIVE MODE - LL_TIM_OC_SetMode(PULL_OUT_TIMER, PULL_OUT_TIMER_CHANNEL, LL_TIM_OCMODE_FORCED_INACTIVE); - - //disable DMA interrupts - //furi_hal_interrupt_set_isr(PULL_OUT_DMA_CH1_IRQ, NULL, NULL); - //LL_DMA_DisableIT_HT(PULL_OUT_DMA_CH1_DEF); - //LL_DMA_DisableIT_TC(PULL_OUT_DMA_CH1_DEF); - - //disable DMA channels & requests - LL_TIM_DisableDMAReq_UPDATE(PULL_OUT_TIMER); - LL_DMA_DisableChannel(PULL_OUT_DMA_CH1_DEF); //need to disable when using normal mode? - LL_DMA_DisableChannel(PULL_OUT_DMA_CH2_DEF); //need to disable when using normal mode? - //LL_TIM_DisableAllOutputs(PULL_OUT_TIMER); //no need to disable when reconfiguring pin? - - //for logging both ccr & arr times during emulate (iso only arr) - LL_TIM_DisableIT_CC3(PULL_OUT_TIMER); - - //switch carrier detection back to input capture for stable readings (required for command detection) - lfrfid_hitag_worker_carrier_in_IC_mode(capture_context); -} - -static void lfrfid_hitag_worker_pull_out_dma_isr(void* capture_context) { - /* currently no HT interrupt enabled, only TC - if(LL_DMA_IsActiveFlag_HT1(PULL_OUT_DMA)) { - LL_DMA_ClearFlag_HT1(PULL_OUT_DMA); - //lfrfid_hitag_worker_pull_out_dma_stop(); - } - */ - - if(LL_DMA_IsActiveFlag_TC1(PULL_OUT_DMA)) { - LL_DMA_ClearFlag_TC1(PULL_OUT_DMA); - lfrfid_hitag_worker_pull_out_dma_stop(capture_context); - } -} - -static void lfrfid_hitag_worker_pull_out_dma_start(size_t length) { - //array pointers remain the same, but length changes so - //reset DMA length (only possible when DMA channel is disabled - //this also resets the DMA counter (the DMA length is the 'remaining counter' - LL_DMA_SetDataLength(PULL_OUT_DMA_CH1_DEF, length); - LL_DMA_SetDataLength(PULL_OUT_DMA_CH2_DEF, length); - - //enable DMA - LL_DMA_EnableChannel(PULL_OUT_DMA_CH1_DEF); - LL_DMA_EnableChannel(PULL_OUT_DMA_CH2_DEF); - - //set OC to PWM1 mode - LL_TIM_OC_SetMode( - PULL_OUT_TIMER, - PULL_OUT_TIMER_CHANNEL, - LL_TIM_OCMODE_PWM1); //during emulate (dma controlled) mode - - //only enable DMA requests on timer update after enabling both channels (otherwise you risk that first DMA request only triggers one of the channels) - LL_TIM_EnableDMAReq_UPDATE(PULL_OUT_TIMER); - - //for logging both ccr & arr times during emulate (iso only arr) - LL_TIM_EnableIT_CC3(PULL_OUT_TIMER); -} - -static void lfrfid_hitag_worker_pull_out_dma_setup( - uint32_t* duration, - uint32_t* pulse, - size_t length, - void* capture_context) { - //DMA setup - LL_TIM_DisableDMAReq_UPDATE(PULL_OUT_TIMER); //start with DMA requests disabled - - // configure DMA "mem -> ARR" channel - LL_DMA_InitTypeDef dma_config = {0}; - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (PULL_OUT_TIMER->ARR); - dma_config.MemoryOrM2MDstAddress = (uint32_t)duration; - dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - //dma_config.Mode = LL_DMA_MODE_CIRCULAR; //keep cycling through memory array - dma_config.Mode = LL_DMA_MODE_NORMAL; //cycle only once through memory array - 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 = length; - dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - dma_config.Priority = LL_DMA_MODE_NORMAL; - LL_DMA_Init(PULL_OUT_DMA_CH1_DEF, &dma_config); - - // configure DMA "mem -> CCR3" channel - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (PULL_OUT_TIMER->CCR3); - dma_config.MemoryOrM2MDstAddress = (uint32_t)pulse; - dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - //dma_config.Mode = LL_DMA_MODE_CIRCULAR; //keep cycling through memory array - dma_config.Mode = LL_DMA_MODE_NORMAL; //cycle only once through memory array - 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 = length; - dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - dma_config.Priority = LL_DMA_MODE_NORMAL; - LL_DMA_Init(PULL_OUT_DMA_CH2_DEF, &dma_config); - - // enable DMA interrupts - furi_hal_interrupt_set_isr( - PULL_OUT_DMA_CH1_IRQ, lfrfid_hitag_worker_pull_out_dma_isr, capture_context); - //LL_DMA_EnableIT_HT(PULL_OUT_DMA_CH1_DEF); //let's try normal DMA mode and only transfer complete interrupt - LL_DMA_EnableIT_TC(PULL_OUT_DMA_CH1_DEF); -} - -//------------------------------------------------------------------ EMULATE TAG: carrier input TIMER & interrupt functions (switching input capture & ETR)------------------------------------------------------------------ - -static void lfrfid_hitag_worker_carrier_in_isr(void* capture_context) { - if(LL_TIM_IsActiveFlag_CC1(CARRIER_IN_TIMER)) { - uint32_t newTIMval = LL_TIM_IC_GetCaptureCH1(CARRIER_IN_TIMER); - LL_TIM_ClearFlag_CC1(CARRIER_IN_TIMER); - - //ARR for TIM2 in input capture mode is UINT32_MAX so at freq of 1MHz this is 70+ minutes, instead of resetting counter, just store prev value and take difference of both - //this will increase measurement accuracy, since both values are then purely hardware controlled, no dependency of interrupt timing to (re)set any timer values - //LL_TIM_SetCounter(CARRIER_IN_TIMER, 0); - - LfRfidHitagCaptureData* capData = capture_context; - capData->capCounter++; - //pack varint (as part of a pair) - //cleaner would be to pack as a varint iso varint_pair, though i'm not sure if I can get the data & size of a packed varint - varint_pair_pack(capData->pair, true, newTIMval - capData->prevTIMval); - //but send anyhow, doesn't matter that it's not a pair - buffer_stream_send_from_isr( - capData->stream, - varint_pair_get_data(capData->pair), - varint_pair_get_size(capData->pair)); - varint_pair_reset(capData->pair); - - capData->prevTIMval = newTIMval; - } else if(LL_TIM_IsActiveFlag_UPDATE(CARRIER_IN_TIMER)) { - uint32_t newTIMval = LL_TIM_GetCounter(CARRIER_IN_REFERENCE_TIMER); - LL_TIM_ClearFlag_UPDATE(CARRIER_IN_TIMER); - - //ARR for ref timer in ETR mode is UINT32_MAX so at freq of 1MHz this is 70+ minutes, instead of resetting counter, just store prev value and take difference of both - //this will increase measurement accuracy, since both values are then purely hardware controlled, no dependency of interrupt timing to (re)set any timer values - LL_TIM_SetCounter(CARRIER_IN_REFERENCE_TIMER, 0); - - LfRfidHitagCaptureData* capData = capture_context; - capData->capCounter++; - //pack varint (as part of a pair) - //cleaner would be to pack as a varint iso varint_pair, though i'm not sure if I can get the data & size of a packed varint - varint_pair_pack(capData->pair, true, newTIMval); // - capData->prevTIMval); - //but send anyhow, doesn't matter that it's not a pair - buffer_stream_send_from_isr( - capData->stream, - varint_pair_get_data(capData->pair), - varint_pair_get_size(capData->pair)); - varint_pair_reset(capData->pair); - - //capData->prevTIMval = newTIMval; - } else if(LL_TIM_IsActiveFlag_CC3(CARRIER_IN_TIMER)) { - uint32_t newTIMval = LL_TIM_GetCounter(CARRIER_IN_REFERENCE_TIMER); - LL_TIM_ClearFlag_CC3(CARRIER_IN_TIMER); - - //don't reset timer for the ccr (HIGH) timing, this way the arr (duration) timing is still full duraton timing (iso only LOW timing) - //LL_TIM_SetCounter(CARRIER_IN_REFERENCE_TIMER, 0); - - LfRfidHitagCaptureData* capData = capture_context; - capData->capCounter++; - //pack varint (as part of a pair) - //cleaner would be to pack as a varint iso varint_pair, though i'm not sure if I can get the data & size of a packed varint - varint_pair_pack(capData->pair, true, newTIMval); // - capData->prevTIMval); - //but send anyhow, doesn't matter that it's not a pair - buffer_stream_send_from_isr( - capData->stream, - varint_pair_get_data(capData->pair), - varint_pair_get_size(capData->pair)); - varint_pair_reset(capData->pair); - - //capData->prevTIMval = newTIMval; - } -} - -static void lfrfid_hitag_worker_carrier_in_ETR_mode(void* capture_context, uint8_t ext_prescaler) { - //disable counters temporarily - LL_TIM_DisableCounter(CARRIER_IN_TIMER); - LL_TIM_DisableCounter(CARRIER_IN_REFERENCE_TIMER); - - //disable Input capture & related interrupt - LL_TIM_DisableIT_CC1(CARRIER_IN_TIMER); - LL_TIM_CC_DisableChannel(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH); - - //reset capdata prev value - LfRfidHitagCaptureData* capData = capture_context; - capData->prevTIMval = 0; - - //switch clocksource to ETR with external prescaling via ARR - LL_TIM_SetPrescaler(CARRIER_IN_TIMER, 1 - 1); //prescaler is only applied at next update event - LL_TIM_GenerateEvent_UPDATE( - CARRIER_IN_TIMER); //prescaler is only applied at next update event //TODO: how to prevent this from creating a capdata entry - LL_TIM_SetAutoReload(CARRIER_IN_TIMER, ext_prescaler - 1); - LL_TIM_SetClockSource(CARRIER_IN_TIMER, LL_TIM_CLOCKSOURCE_EXT_MODE2); - - //reconfigure carrier_in pin to TIM2 ETR - furi_hal_gpio_init_ex( - &gpio_rfid_carrier, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn2TIM2); - - //reset timer counter & capture context for period calculation - LL_TIM_SetCounter(CARRIER_IN_TIMER, 0); - LL_TIM_SetCounter(CARRIER_IN_REFERENCE_TIMER, 0); - - //enable interrupt via update - LL_TIM_EnableIT_UPDATE(CARRIER_IN_TIMER); - - //re-enable counters - LL_TIM_EnableCounter(CARRIER_IN_TIMER); - LL_TIM_EnableCounter(CARRIER_IN_REFERENCE_TIMER); -} - -static void lfrfid_hitag_worker_carrier_in_IC_mode(void* capture_context) { - //disable counters temporarily - LL_TIM_DisableCounter(CARRIER_IN_TIMER); - LL_TIM_DisableCounter(CARRIER_IN_REFERENCE_TIMER); - - //disable interrupt via update - LL_TIM_DisableIT_UPDATE(CARRIER_IN_TIMER); - - //reset capdata prev value - LfRfidHitagCaptureData* capData = capture_context; - capData->prevTIMval = 0; - - //switch clocksource to system/64, external prescaling is handled in IC prescaler - LL_TIM_SetClockSource(CARRIER_IN_TIMER, LL_TIM_CLOCKSOURCE_INTERNAL); - LL_TIM_SetPrescaler(CARRIER_IN_TIMER, 64 - 1); //prescaler is only applied at next update event - LL_TIM_GenerateEvent_UPDATE( - CARRIER_IN_TIMER); //prescaler is only applied at next update event //TODO: how to prevent this from creating a capdata entry - LL_TIM_SetAutoReload(CARRIER_IN_TIMER, UINT32_MAX); - - //reconfigure carrier_in pin to TIM2 CH1 for input capture - furi_hal_gpio_init_ex( - &gpio_rfid_carrier, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); - - //reset timer counter & capture context for period calculation - LL_TIM_SetCounter(CARRIER_IN_TIMER, 0); - LL_TIM_SetCounter(CARRIER_IN_REFERENCE_TIMER, 0); - - //enable Input capture & related interrupt - LL_TIM_EnableIT_CC1(CARRIER_IN_TIMER); - LL_TIM_CC_EnableChannel(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH); - - //re-enable counters - LL_TIM_EnableCounter(CARRIER_IN_TIMER); - LL_TIM_EnableCounter(CARRIER_IN_REFERENCE_TIMER); -} - -static void lfrfid_hitag_worker_carrier_in_start( - void* capture_context, - uint8_t ext_prescaler, - uint32_t* duration, - uint32_t* pulse, - size_t length) { - FURI_CRITICAL_ENTER(); - LL_DMA_DeInit(PULL_OUT_DMA_CH1_DEF); //required? - LL_DMA_DeInit(PULL_OUT_DMA_CH2_DEF); //required? - LL_TIM_DeInit(CARRIER_IN_TIMER); - LL_TIM_DeInit(CARRIER_IN_REFERENCE_TIMER); - FURI_CRITICAL_EXIT(); - - //setup reference timer: simple setup with base freq of 1MHz and max autoreload - LL_TIM_InitTypeDef TIM_InitStruct_Ref = {0}; - TIM_InitStruct_Ref.Prescaler = - 64 - - 1; //system base freq is 64MHz, so this sets base freq for TIM to 1MHz (aka 1us period) - TIM_InitStruct_Ref.CounterMode = LL_TIM_COUNTERMODE_UP; - TIM_InitStruct_Ref.Autoreload = UINT32_MAX; - TIM_InitStruct_Ref.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; - LL_TIM_Init(CARRIER_IN_REFERENCE_TIMER, &TIM_InitStruct_Ref); - - //setup carrier in timer for input capture - LL_TIM_InitTypeDef TIM_InitStruct = {0}; - TIM_InitStruct.Prescaler = - 64 - - 1; //system base freq is 64MHz, so this sets base freq for TIM to 1MHz (aka 1us period) - TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; - TIM_InitStruct.Autoreload = UINT32_MAX; - TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; - LL_TIM_Init(CARRIER_IN_TIMER, &TIM_InitStruct); - - LL_TIM_DisableARRPreload(CARRIER_IN_TIMER); - LL_TIM_SetClockSource( - CARRIER_IN_TIMER, - LL_TIM_CLOCKSOURCE_INTERNAL); //default is internal, so likely not required - LL_TIM_DisableDMAReq_TRIG(CARRIER_IN_TIMER); - LL_TIM_DisableIT_TRIG(CARRIER_IN_TIMER); - - //meanwhile already prepre the ETR - LL_TIM_ConfigETR( - CARRIER_IN_TIMER, - LL_TIM_ETR_POLARITY_INVERTED, - LL_TIM_ETR_PRESCALER_DIV1, - LL_TIM_ETR_FILTER_FDIV1); - - //INPUT CAPTURE SETUP - // Timer: channel 1 direct (from GPIO) - LL_TIM_IC_SetActiveInput( - CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH, LL_TIM_ACTIVEINPUT_DIRECTTI); - //prescaling direct channel seems to be working fine (and is necessary since otherwise sd write cannot keep up) - if(ext_prescaler == 4) { - LL_TIM_IC_SetPrescaler(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH, LL_TIM_ICPSC_DIV4); - } else if(ext_prescaler == 2) { - LL_TIM_IC_SetPrescaler(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH, LL_TIM_ICPSC_DIV2); - } else { - LL_TIM_IC_SetPrescaler(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH, LL_TIM_ICPSC_DIV1); - } - LL_TIM_IC_SetPolarity(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH, LL_TIM_IC_POLARITY_RISING); - LL_TIM_IC_SetFilter(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH, LL_TIM_IC_FILTER_FDIV1); - /* Timer: channel 2 indirect (from channel 1) - // only measure & write to file the period (not the on & off cycle), this reduced CPU load on SD write to (hopefully) keep up with period measurements without prescaler - LL_TIM_IC_SetActiveInput(CARRIER_IN_TIMER, CARRIER_IN_TIMER_IND_CH, LL_TIM_ACTIVEINPUT_INDIRECTTI); - //presaling indirect channel doesn't really work well yet :/ - LL_TIM_IC_SetPrescaler(CARRIER_IN_TIMER, CARRIER_IN_TIMER_IND_CH, LL_TIM_ICPSC_DIV2); - LL_TIM_IC_SetPolarity(CARRIER_IN_TIMER, CARRIER_IN_TIMER_IND_CH, LL_TIM_IC_POLARITY_FALLING); - LL_TIM_IC_SetFilter(CARRIER_IN_TIMER, CARRIER_IN_TIMER_IND_CH, LL_TIM_IC_FILTER_FDIV1); - */ - - //set interrupt callback for capturing period - LL_TIM_EnableIT_CC1(CARRIER_IN_TIMER); - //LL_TIM_EnableIT_CC2(CARRIER_IN_TIMER); - furi_hal_interrupt_set_isr( - FuriHalInterruptIdTIM2, lfrfid_hitag_worker_carrier_in_isr, capture_context); - - //OUTPUT COMPARE SETUP - LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; - //TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; //during emulate (dma controlled) mode - TIM_OC_InitStruct.OCMode = - LL_TIM_OCMODE_FORCED_INACTIVE; //during carrier in put output to forced low state - TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; - TIM_OC_InitStruct.CompareValue = - 0; //0% this should have almost similar effect as keeping output forced inactive (there's still some micropulse emited, going high at ARR and immediately down again at CCR value) - LL_TIM_OC_Init(PULL_OUT_TIMER, PULL_OUT_TIMER_CHANNEL, &TIM_OC_InitStruct); - LL_TIM_OC_SetPolarity( - PULL_OUT_TIMER, - PULL_OUT_TIMER_CHANNEL, - LL_TIM_OCPOLARITY_HIGH); //active high (gpio goes high when pulse is high) - - //INIT DMA (do not start it yet) - lfrfid_hitag_worker_pull_out_dma_setup(duration, pulse, length, capture_context); - - LL_TIM_CC_EnableChannel(CARRIER_IN_TIMER, CARRIER_IN_TIMER_DIR_CH); - //LL_TIM_CC_EnableChannel(CARRIER_IN_TIMER, CARRIER_IN_TIMER_IND_CH); - LL_TIM_CC_EnableChannel(PULL_OUT_TIMER, PULL_OUT_TIMER_CHANNEL); - LL_TIM_SetCounter(CARRIER_IN_TIMER, 0); - LL_TIM_SetCounter(CARRIER_IN_REFERENCE_TIMER, 0); - LL_TIM_EnableCounter(CARRIER_IN_TIMER); - LL_TIM_EnableCounter(CARRIER_IN_REFERENCE_TIMER); -} - -void lfrfid_hitag_worker_carrier_in_stop() { - furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); - - LL_TIM_DisableCounter(CARRIER_IN_TIMER); - LL_TIM_DisableCounter(CARRIER_IN_REFERENCE_TIMER); - LL_TIM_DisableAllOutputs(CARRIER_IN_TIMER); //used for pull pin OC - - FURI_CRITICAL_ENTER(); - LL_DMA_DeInit(PULL_OUT_DMA_CH1_DEF); - LL_DMA_DeInit(PULL_OUT_DMA_CH2_DEF); - LL_TIM_DeInit(CARRIER_IN_TIMER); - LL_TIM_DeInit(CARRIER_IN_REFERENCE_TIMER); - FURI_CRITICAL_EXIT(); -} - -//------------------------------------------------------------------ hitag data parsing functions ------------------------------------------------------------------ - -static void lfrfid_hitag_worker_yield_dma_buffer_BPLM( - LfRfidHitagCMDData* dataCMD, - LfRfidHitagCMDData* backupCMD, - TimerDMAData* dataDMA, - size_t start, - size_t len) { - //based on interal timer with freq of 1MHz (period of 1us) - //use active command - LfRfidHitagCMDData* data = dataCMD; - if(!data->CMDactive) { - data = backupCMD; - } - uint16_t lastarrposition = 0; - size_t toskip = 0; - bool itemProcessed = true; - size_t s = start; - //for ( ; data->CMDloop || data->CMDposition < data->CMDlength ; data->CMDposition++){ - while(1) { - //at the end of current command (real or backup), check if there's unprocessed CMD available, if not switch to backup - if(data->CMDposition == data->CMDlength) { - data->CMDcount++; - if(dataCMD->CMDposition < - dataCMD - ->CMDlength) { //currently backup & new unprocessed command available --> switch over to real CMD - data->CMDactive = false; - data = dataCMD; - data->CMDactive = true; - data->CMDcount = 0; - } else if(dataCMD - ->CMDloop) { //if looping (real or backup) --> reset real (current) CMD to startposition - data->CMDposition = 0; - data->CMDsubposition = 0; - } else if(dataCMD - ->CMDactive) { //real command and not looping --> switch to backup CMD and reset backup CMD to startposition - data->CMDactive = false; - data = backupCMD; - data->CMDactive = true; - data->CMDposition = 0; - data->CMDsubposition = 0; - data->CMDcount = 0; - } else { //backupCMD but set to not loop --> loop anyway: reset to startposition - data->CMDposition = 0; - data->CMDsubposition = 0; - } - } - //start processing the next CMD item - lastarrposition = s; - if(s == start) { - toskip = data->CMDsubposition; - } else { - toskip = 0; - data->CMDsubposition = 0; - } - switch(data->CMDtype[data->CMDposition]) { - case HITAG_STOP: //just 1 STOP cycle (HITAG_LOW lows + HITAG_STOP_HIGH highs) - if(s > start) { - dataDMA->timer_buffer_arr[s - 1] += HITAG_LOW * HITAG_BASEPERIOD; - } - for(size_t t = 0; t < HITAG_STOP_HIGH - toskip; t++) { - if(s == len + start) { - //arrays are filled, do not continue out - itemProcessed = false; - break; - } - dataDMA->timer_buffer_ccr[s] = HITAG_BASEPERIOD / 2; - dataDMA->timer_buffer_arr[s] = HITAG_BASEPERIOD - 1; - s++; - } - break; - case HITAG_ON: //x ON cycles --> update next X arr/ccr values - for(size_t x = 0; x < data->CMDvalue[data->CMDposition] - toskip; x++) { - if(s == len + start) { - //arrays are filled, do not continue out - itemProcessed = false; - break; - } - dataDMA->timer_buffer_ccr[s] = HITAG_BASEPERIOD / 2; - dataDMA->timer_buffer_arr[s] = HITAG_BASEPERIOD - 1; - s++; - } - break; - case HITAG_OFF: //x OFF cycles (update previous arr value) - if(s > start) { - dataDMA->timer_buffer_arr[s - 1] += - data->CMDvalue[data->CMDposition] * HITAG_BASEPERIOD; - } - break; - default: // <=HITAG_BYTE: //bits to be sent - //get each bit & update arr/ccr accordingly (7 lows + HITAG_0_HIGH/HITAG_1_HIGH highs) - for(size_t b = 8 - data->CMDtype[data->CMDposition]; b < 8; b++) { - if(s > start) { - dataDMA->timer_buffer_arr[s - 1] += HITAG_LOW * HITAG_BASEPERIOD; - } - if(0x80 & (data->CMDvalue[data->CMDposition] << b)) { - for(size_t t = 0; t < HITAG_1_HIGH; t++) { - if(s == len + start) { - //arrays are filled, do not continue out - itemProcessed = false; - break; - } - dataDMA->timer_buffer_ccr[s] = HITAG_BASEPERIOD / 2; - dataDMA->timer_buffer_arr[s] = HITAG_BASEPERIOD - 1; - s++; - if(toskip > 0) { - toskip--; - s--; - } - } - } else { - for(size_t t = 0; t < HITAG_0_HIGH; t++) { - if(s == len + start) { - //arrays are filled, do not continue out - itemProcessed = false; - break; - } - dataDMA->timer_buffer_ccr[s] = HITAG_BASEPERIOD / 2; - dataDMA->timer_buffer_arr[s] = HITAG_BASEPERIOD - 1; - s++; - if(toskip > 0) { - toskip--; - s--; - } - } - } - if(!itemProcessed) { - break; - } - } - break; - } - if(!itemProcessed) { - break; - } - data->CMDposition++; - } - - /*if buffer is not full, but command is fully processed: fill remainder of buffer with normal on cycles - if (stimer_buffer_ccr[s]=HITAG_BASEPERIOD/4; //25%duty cycle - dataDMA->timer_buffer_arr[s]=1*HITAG_BASEPERIOD-1; - } else { - dataDMA->timer_buffer_ccr[s]=HITAG_BASEPERIOD/2; - dataDMA->timer_buffer_arr[s]=HITAG_BASEPERIOD-1; - } - } - //other stuff to do? - } - */ - - //if buffer was full, but command not yet fully processed: set starting position for next time: - if(!itemProcessed) { - data->CMDsubposition += (len + start) - lastarrposition; - } else { - data->CMDsubposition = 0; - } -} - -static uint16_t lfrfid_hitag_worker_yield_dma_buffer_MC( - LfRfidHitagReplyData* dataReply, - TimerDMAData* dataDMA, - uint8_t carrierPrescaler) { - //based on external carrier with freq of 125kHz (period of 8us) - - uint16_t dma_len = 0; - - bool bit = false; - bool prevBit = true; //must be true to initialze MC yield for first bit - - //process reply bits and add HIGH-LOW for 1 or LOW-HIGH for 0 - for(uint8_t i = 0; i < dataReply->replylength; i++) { - for(int8_t b = dataReply->replytype[i] - 1; b >= 0; b--) { - bit = (dataReply->replyvalue[i] >> b & 0b00000001); - if(bit) { //HIGH-LOW - if(prevBit) { //if first bit (initial value of prevBit=true) or if prev bit was 1: start of new default cycle - dataDMA->timer_buffer_arr[dma_len] = 32 - 1; - dataDMA->timer_buffer_ccr[dma_len] = 16; - dma_len++; - } else { //previous bit was 0, so extend the ccr and arr with 16 - dataDMA->timer_buffer_arr[dma_len - 1] += 16; - dataDMA->timer_buffer_ccr[dma_len - 1] += 16; - } - } else { //LOW-HIGH - if(prevBit) { //previous bit was 1 so extend the LOW (arr only) of prev cycle with 16 and add new default cycle - dataDMA->timer_buffer_arr[dma_len - 1] += 16; - - dataDMA->timer_buffer_arr[dma_len] = 32 - 1; - dataDMA->timer_buffer_ccr[dma_len] = 16; - dma_len++; - } else { //previous bit was 0 so just add new default cycle - dataDMA->timer_buffer_arr[dma_len] = 32 - 1; - dataDMA->timer_buffer_ccr[dma_len] = 16; - dma_len++; - } - } - prevBit = bit; - } - } - - //add extra period, to reset timer back to normal carrier_in detection after reply has finished - //also required to make sure the last real dma values are fully executed before dma signals transfer complete, which resets worker into command detection - dataDMA->timer_buffer_arr[dma_len] = carrierPrescaler - 1; - dataDMA->timer_buffer_ccr[dma_len] = 0; - dma_len++; - - return dma_len; -} - -static uint16_t lfrfid_hitag_worker_yield_dma_buffer_AC( - LfRfidHitagReplyData* dataReply, - TimerDMAData* dataDMA, - uint8_t carrierPrescaler) { - //based on external carrier with freq of 125kHz (period of 8us) - - uint16_t dma_len = 0; - - //process reply bits and add 2 short pulses for 1 or 1 long pulse for 0 - for(uint8_t i = 0; i < dataReply->replylength; i++) { - for(int8_t b = dataReply->replytype[i] - 1; b >= 0; b--) { - if(dataReply->replyvalue[i] >> b & 0b00000001) { - dataDMA->timer_buffer_arr[dma_len] = 32 - 1; - dataDMA->timer_buffer_ccr[dma_len] = 16; //16 - dma_len++; - dataDMA->timer_buffer_arr[dma_len] = 32 - 1; - dataDMA->timer_buffer_ccr[dma_len] = 16; //16 - dma_len++; - } else { - dataDMA->timer_buffer_arr[dma_len] = 64 - 1; - dataDMA->timer_buffer_ccr[dma_len] = 32; //32 - dma_len++; - } - } - } - - //add extra period, to reset timer back to normal carrier_in detection after reply has finished - //also required to make sure the last real dma values are fully executed before dma signals transfer complete, which resets worker into command detection - dataDMA->timer_buffer_arr[dma_len] = carrierPrescaler - 1; - dataDMA->timer_buffer_ccr[dma_len] = 0; - dma_len++; - - return dma_len; -} - -static void lfrfid_hitag_worker_decoder_feed( - uint32_t pulse, - uint32_t duration, - uint32_t* memBlock, - uint8_t* bits, - bool* half, - bool* finished, - LfRfidHitagProtocol protocol, - uint8_t startBits) { - uint32_t bit = 1 << (32 - 1); - if(*bits == 0) { - *half = false; - } - if(protocol == HitagProtocolManchesterCoding) { - if(HITAG_DURATION_S * (1 - HITAG_DURATION_ERROR_MARGIN) <= duration && - duration <= HITAG_DURATION_S * (1 + HITAG_DURATION_ERROR_MARGIN)) { - //short period (HL) detected: same bit as previous - if(*bits < startBits) { - //don't store startbits in memory, just increase bitcounter - (*bits)++; - } else if(*bits == startBits) { - //same as previous bit (last startbit it 1, so add 1 to the memBlock) - memBlock[(*bits - startBits) / 32] |= (bit >> (*bits - startBits) % 32); //add 1 - (*bits)++; - } else { - //add same as previous bit - if(memBlock[(*bits - startBits - 1) / 32] & - (bit >> (*bits - startBits - 1) % 32)) { - memBlock[(*bits - startBits) / 32] |= - (bit >> (*bits - startBits) % 32); //add 1 - (*bits)++; - } else { - (*bits)++; //no mem action for 0, just increase bitcounter - } - } - } else if( - HITAG_DURATION_M * (1 - HITAG_DURATION_ERROR_MARGIN) <= duration && - duration <= HITAG_DURATION_M * (1 + HITAG_DURATION_ERROR_MARGIN)) { - //medium period (H|HL or HL|L) detected: half 0 + full 1 or full 1 + half 0 detected - if(*bits < startBits) { - //assuming all startbits are 1's, this can only happen on last startbit followed by 0 - //don't store startbits, no mem action for 0, just increase bitcounter twice - (*bits)++; - (*bits)++; - *half = true; - } else if(*bits == startBits) { - //assuming all startbits were 1's, this is a data 10 detection - memBlock[(*bits - startBits) / 32] |= (bit >> (*bits - startBits) % 32); //add 1 - (*bits)++; - (*bits)++; //no mem action for 0, just increase bitcounter - *half = true; - } else { - //if we're in half of a bit, add 1, else add 10 and set half flag - if(*half) { - memBlock[(*bits - startBits) / 32] |= - (bit >> (*bits - startBits) % 32); //add 1 - (*bits)++; - *half = false; - } else { - memBlock[(*bits - startBits) / 32] |= - (bit >> (*bits - startBits) % 32); //add 1 - (*bits)++; - (*bits)++; //no mem action for 0, just increase bitcounter - *half = true; - } - } - } else if( - HITAG_DURATION_L * (1 - HITAG_DURATION_ERROR_MARGIN) <= duration && - duration <= HITAG_DURATION_L * (1 + HITAG_DURATION_ERROR_MARGIN)) { - //long period (H|HL|L) detected: half 0 + full 1 + half 0 detected - if(*bits <= startBits) { - //TODO: throw error since this can't happen till after first real bit, since all startbits should be 1's - } else { - //TODO check that prev bit is indeed 0 - //add 10 to the memBlock - memBlock[(*bits - startBits) / 32] |= (bit >> (*bits - startBits) % 32); //add 1 - (*bits)++; - (*bits)++; //no mem action for 0, just increase bitcounter - } - } else if(HITAG_DURATION_L * (1 + HITAG_DURATION_ERROR_MARGIN) < duration) { - //end of modulation by tag, check if this pulse duration includes a bit - if(*bits > 0) { - if(*half && pulse < HITAG_DURATION_S) { - //second half of 0 bit, no action - } else if(*half && pulse < HITAG_DURATION_M) { - //second half or 0 bit + another 1 bit - memBlock[(*bits - startBits) / 32] |= - (bit >> (*bits - startBits) % 32); //add 1 - (*bits)++; - } else if(!(*half) && pulse < HITAG_DURATION_S) { - //another 1 bit - memBlock[(*bits - startBits) / 32] |= - (bit >> (*bits - startBits) % 32); //add 1 - (*bits)++; - } else { - //invalid pulse duration --> reset? - } - *finished = true; - } - } else { - //invalid pulse duration: reset tempData if necessary - if(*bits > 0) { - memset(memBlock, 0, HITAG_BLOCKPAGES * sizeof(memBlock[0])); - *bits = 0; - *finished = false; - } - } - } else if(protocol == HitagProtocolAntiCollision) { - if(HITAG_DURATION_S * (1 - HITAG_DURATION_ERROR_MARGIN) <= duration && - duration <= HITAG_DURATION_S * (1 + HITAG_DURATION_ERROR_MARGIN)) { - //short period (HL): first or second half of a 1 detected - //if first short: add 1 to memBlock and increase half counter - if(*half == false) { - if(*bits >= startBits) { - memBlock[(*bits - startBits) / 32] |= (bit >> (*bits - startBits) % 32); - } - (*bits)++; - *half = true; - } else { - //if second small: reset half counter - *half = false; - } - } else if( - HITAG_DURATION_L * (1 - HITAG_DURATION_ERROR_MARGIN) <= duration && - duration <= HITAG_DURATION_L * (1 + HITAG_DURATION_ERROR_MARGIN)) { - //long period (HHLL): 0 detected - //should only happen after full detection of 1 or after startBits - if(*bits < startBits || *half) { - //reset - memset(memBlock, 0, HITAG_BLOCKPAGES * sizeof(memBlock[0])); - *bits = 0; - *finished = false; - } else { - (*bits)++; //add 0 to memBlock - } - } else if(HITAG_DURATION_L * (1 + HITAG_DURATION_ERROR_MARGIN) < duration) { - //end of modulation by tag, check if this pulse duration includes a bit - if(*bits > 0) { - if(!(*half) && pulse > HITAG_DURATION_S) { - (*bits)++; //add 0 to memBlock - } else { - //invalid pulse duration: reset? - } - *finished = true; - } - } else { - //invalid pulse duration: reset tempData if necessary - if(*bits > 0) { - memset(memBlock, 0, HITAG_BLOCKPAGES * sizeof(memBlock[0])); - *bits = 0; - *finished = false; - } - } - } -} - -static void lfrfid_hitag_worker_calc_crc(uint8_t* crc, uint8_t data, uint8_t bitcount) { - *crc ^= data; // crc = crc (exor) data - do { - if(*crc & 0x80) // if (MSB-CRC == 1) - { - *crc <<= 1; // CRC = CRC bit-shift left - *crc ^= CRC_POLYNOM; // CRC = CRC (exor) CRC_POLYNOM - } else { - *crc <<= 1; // CRC = CRC bit-shift left - } - } while(--bitcount); -} - -static bool lfrfid_hitag_worker_validate_command(uint32_t* bits, uint8_t len) { - if(len == 5 && (bits[0] >> (32 - 5)) == 0b11001) { - //SET_CCNEW - return true; - } else if(len == 5 && (bits[0] >> (32 - 5)) == 0b00110) { - //SET_CC - return true; - } else if(len == 45 && (bits[0] >> (32 - 5)) == 0b00000) { - //SELECT - - //calculate CRC for first 37 bits (4bytes + 5 bits) - uint8_t byte1 = bits[0] >> 24 & 0xff; - uint8_t byte2 = bits[0] >> 16 & 0xff; - uint8_t byte3 = bits[0] >> 8 & 0xff; - uint8_t byte4 = bits[0] & 0xff; - uint8_t byte5 = bits[1] >> 24 & 0b11111000; - uint8_t crcRead = bits[1] >> 19 & 0xff; - - uint8_t crc = CRC_PRESET; - lfrfid_hitag_worker_calc_crc(&crc, byte1, 8); - lfrfid_hitag_worker_calc_crc(&crc, byte2, 8); - lfrfid_hitag_worker_calc_crc(&crc, byte3, 8); - lfrfid_hitag_worker_calc_crc(&crc, byte4, 8); - lfrfid_hitag_worker_calc_crc(&crc, byte5, 5); - - //and compare result with received crc - return (crcRead == crc); - } - //else if (len == 20 && (bits[0]>>31 || (bits[0]>>(32-4)) == 0b0111)){ - else if(len == 20 && bits[0] > 0x70000000) { - //R/W page/block (cmd starting with 1) or HALT (0111) --> so bit0 should be >= 0b01110000 00000000 00000000 00000000 (0x70000000) - //calculate CRC for first 12 bits (1byte + 4 bits) - uint8_t byte1 = bits[0] >> 24 & 0xff; - uint8_t byte2 = bits[0] >> 16 & 0b11110000; - uint8_t crcRead = bits[0] >> 12 & 0xff; - - uint8_t crc = CRC_PRESET; - lfrfid_hitag_worker_calc_crc(&crc, byte1, 8); - lfrfid_hitag_worker_calc_crc(&crc, byte2, 4); - - //and compare result with received crc - return (crcRead == crc); - } - return false; -} - -static bool lfrfid_hitag_worker_validate_bits(uint32_t* bits, uint8_t bitcounter) { - if(47 >= bitcounter && bitcounter >= 45) { - for(uint8_t i = 0; i < bitcounter - 44; i++) { - if(i > 0) { - bits[0] = bits[0] << 1 | bits[1] >> (32 - 1); - bits[1] = bits[1] << 1; - } - if(lfrfid_hitag_worker_validate_command(bits, 45)) return true; - } - } else if(22 >= bitcounter && bitcounter >= 20) { - for(uint8_t i = 0; i < bitcounter - 19; i++) { - if(i > 0) { - bits[0] = bits[0] << 1; // | bits[1]>>(32-1); - //bits[1] = bits[1]<<1; - } - if(lfrfid_hitag_worker_validate_command(bits, 20)) return true; - } - } else if(7 >= bitcounter && bitcounter >= 5) { - for(uint8_t i = 0; i < bitcounter - 4; i++) { - if(i > 0) { - bits[0] = bits[0] << 1; // | bits[1]>>(32-1); - //bits[1] = bits[1]<<1; - } - if(lfrfid_hitag_worker_validate_command(bits, 5)) return true; - } - } - return false; -} - -static uint8_t lfrfid_hitag_worker_validate_ripple_delta(uint32_t delta) { - if(18 <= delta && delta <= 22) return 1; - if(26 <= delta && delta <= 32) return 1; - return 0; -} - -void lfrfid_hitag_worker_initialize_tag_data(LFRFIDHitag* tag) { - //initialize a tag datastructure - //set all pages to 0x00000000 - memset(tag->pageData, 0, sizeof(tag->pageData)); - - //set all pages to unknown - for(uint8_t i = 0; i < HITAG_PAGES; i++) { - tag->pageKnown[i] = 0; - } - - //default RW settings: - memset(tag->pageRW, READ, HITAG_PAGES / 2 * sizeof(tag->pageRW[0])); - memset(tag->pageRW + HITAG_PAGES / 2, READ | WRITE, HITAG_PAGES / 2 * sizeof(tag->pageRW[0])); - tag->pageRW[2] = 0; - tag->pageRW[3] = 0; - - //default public/secret settings: - for(uint8_t i = 0; i < HITAG_PAGES; i++) { - tag->pagePublic[i] = 1; - } - for(uint8_t i = 2; i <= 31; i++) { - tag->pagePublic[i] = 0; - } -} - -static void lfrfid_hitag_worker_combine_tag_data(LFRFIDHitag* tag) { - memcpy(tag->tagData, tag->pageData, HITAG_PAGES * HITAG_PAGEBYTES); - memcpy(tag->tagData + HITAG_PAGES * HITAG_PAGEBYTES, tag->pageKnown, HITAG_PAGES); -} - -static void lfrfid_hitag_worker_split_tag_data(LFRFIDHitag* tag) { - memcpy(tag->pageData, tag->tagData, HITAG_PAGES * HITAG_PAGEBYTES); - memcpy(tag->pageKnown, tag->tagData + HITAG_PAGES * HITAG_PAGEBYTES, HITAG_PAGES); -} - -void lfrfid_hitag_worker_process_config_page(LFRFIDHitag* tag) { - //RW settings via byte 0 - if(tag->pageData[4 + 0] & 0b10000000) { //block 1 (logData) RW or 0 - memset(tag->pageRW + 1 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); - } else { - memset(tag->pageRW + 1 * 4, 0, 4 * sizeof(tag->pageRW[0])); - } - if(tag->pageData[4 + 0] & 0b01000000) { //key A&B (page 2&3) W or 0 - memset(tag->pageRW + 2, WRITE, 2 * sizeof(tag->pageRW[0])); - } else { - memset(tag->pageRW + 2, 0, 2 * sizeof(tag->pageRW[0])); - } - if(tag->pageData[4 + 0] & 0b00100000) { //block 2 RW or RO - memset(tag->pageRW + 2 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); - } else { - memset(tag->pageRW + 2 * 4, READ, 4 * sizeof(tag->pageRW[0])); - } - if(tag->pageData[4 + 0] & 0b00010000) { //block 3 RW or RO - memset(tag->pageRW + 3 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); - } else { - memset(tag->pageRW + 3 * 4, READ, 4 * sizeof(tag->pageRW[0])); - } - if(tag->pageData[4 + 0] & 0b00001000) { //block 4 RW or RO - memset(tag->pageRW + 4 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); - } else { - memset(tag->pageRW + 4 * 4, READ, 4 * sizeof(tag->pageRW[0])); - } - if(tag->pageData[4 + 0] & 0b00000100) { //block 5 RW or RO - memset(tag->pageRW + 5 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); - } else { - memset(tag->pageRW + 5 * 4, READ, 4 * sizeof(tag->pageRW[0])); - } - if(tag->pageData[4 + 0] & 0b00000010) { //block 6 RW or RO - memset(tag->pageRW + 6 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); - } else { - memset(tag->pageRW + 6 * 4, READ, 4 * sizeof(tag->pageRW[0])); - } - if(tag->pageData[4 + 0] & 0b00000001) { //block 7 RW or RO - memset(tag->pageRW + 7 * 4, READ | WRITE, 4 * sizeof(tag->pageRW[0])); - } else { - memset(tag->pageRW + 7 * 4, READ, 4 * sizeof(tag->pageRW[0])); - } - //RW settings via byte 1 - if(tag->pageData[4 + 1] & 0b00010000) { //config page (1) RW or RO - tag->pageRW[1] = READ / WRITE; - } else { - tag->pageRW[1] = READ; - } - - //public/secret settings via byte 1 - if(tag->pageData[4 + 1] & 0b00000001) { //block 4-7 public or secret - for(uint8_t i = 4 * 4; i < 8 * 4; i++) { - tag->pagePublic[i] = 1; - } - } else { - for(uint8_t i = 4 * 4; i < 8 * 4; i++) { - tag->pagePublic[i] = 0; - } - } -} - -//------------------------------------------------------------------ worker threads ------------------------------------------------------------------ - -static int32_t lfrfid_hitag_worker_emulate_thread(void* thread_context) { - LFRFIDHitagWorker* worker = (LFRFIDHitagWorker*)thread_context; - - //load tag - worker->tag = malloc(sizeof(LFRFIDHitag)); - lfrfid_hitag_worker_initialize_tag_data(worker->tag); - - size_t data_size = protocol_dict_get_data_size(worker->dict, LFRFIDProtocolHitag1); - protocol_dict_get_data(worker->dict, LFRFIDProtocolHitag1, worker->tag->tagData, data_size); - - lfrfid_hitag_worker_split_tag_data(worker->tag); - //TODO: check if a tag has been properly loaded with known serial nr & config page - lfrfid_hitag_worker_process_config_page(worker->tag); - - //init capData for carrier_in - LfRfidHitagCaptureData* capData = malloc(sizeof(LfRfidHitagCaptureData)); - capData->stream = buffer_stream_alloc(EMULATE_BUFFER_SIZE, EMULATE_BUFFER_COUNT); - capData->pair = varint_pair_alloc(); - capData->capCounter = 0; - capData->prevTIMval = 0; - - //init dmaData for emulate mode: - uint16_t dmaLen = 0; - TimerDMAData* dataDMA = malloc(sizeof(TimerDMAData)); - - LfRfidHitagReplyData* dataReply = malloc(sizeof(LfRfidHitagReplyData)); - - //set pins to read setup - furi_hal_rfid_pins_read(); - //reconfigure carrier_out to fixed low state instead - furi_hal_gpio_init(&gpio_rfid_carrier_out, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_write(&gpio_rfid_carrier_out, false); - //reconfigure PA15 carrier_in to alt ftn 1 (TIM2 CH1) to use it for input capture - furi_hal_gpio_init_ex( - &gpio_rfid_carrier, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); - //reconfigure pull to alternate function 1 (TIM2 CH3) to drive low (for read carrier) or pull antenna via DMA (for emulation) - furi_hal_gpio_init_ex( - &gpio_nfc_irq_rfid_pull, - GpioModeAltFunctionPushPull, - GpioPullNo, - GpioSpeedLow, - GpioAltFn1TIM2); - - //start capture via carrier in instead of rfid in - lfrfid_hitag_worker_carrier_in_start( - capData, - worker->carrierPrescaler, - dataDMA->timer_buffer_arr, - dataDMA->timer_buffer_ccr, - dmaLen); - - uint8_t prescaler = worker->carrierPrescaler; - uint8_t period = 8 * prescaler - 1; - - bool carrier_on = false; - int8_t carrier_count = 0; - uint8_t carrier_threshold_on = 5; - int8_t carrier_threshold_off = -5; - - uint32_t periodcounter = 0; - - uint8_t localMax = 0; - uint8_t localMin = 255; - uint32_t localMaxIndex = 0; - uint32_t localMinIndex = 0; - uint32_t lastMaxIndex = 0; - uint32_t lastMinIndex = 0; - uint8_t maxBitCounter = 0; - uint8_t minBitCounter = 0; - uint32_t maxBits[5]; - uint32_t minBits[5]; - int8_t cmd = 0; - uint32_t one = 1 << (32 - 1); - uint32_t cmdIndex = 0; - - uint32_t miniMaxDelta = 0; - uint32_t miniMaxIndex = 0; - uint32_t prevMaxDelta = 0; - uint32_t maxDelta = 0; - - uint32_t miniMinDelta = 0; - uint32_t miniMinIndex = 0; - uint32_t prevMinDelta = 0; - uint32_t minDelta = 0; - - uint32_t prevduration = 0; - uint32_t duration = 0; - uint8_t durationcounter = 0; - uint8_t durationthreshold = 5; - - uint8_t tagState = HitagStateIdle; - uint8_t tagMode = HitagModeBasic; - - while(1) { - Buffer* buffer = buffer_stream_receive(capData->stream, 1); - - if(buffer != NULL) { - size_t buffsize = buffer_get_size(buffer); - uint8_t* buffdata = buffer_get_data(buffer); - - //parse carrier_in duration & scan for cmds - size_t index = 0; - while(index < buffsize) { - prevduration = duration; - size_t tmp_size = - varint_uint32_unpack(&duration, &buffdata[index], buffsize - index); - - if(tmp_size == 0) { - FURI_LOG_E(TAG, "can't unpack varint pair"); - break; - } else { - index += tmp_size; - - periodcounter++; - - if(!carrier_on) { //detect if carrier is available - if(0.75 * period <= duration && duration <= 1.25 * period) { - carrier_count++; - if(carrier_count >= carrier_threshold_on) { - carrier_on = true; - localMax = 0; - localMin = 255; - minBitCounter = 0; - maxBitCounter = 0; - minBits[0] = 0; - minBits[1] = 0; - maxBits[0] = 0; - maxBits[1] = 0; - - carrier_count = 0; - //TODO reset tagstate to Idle & tagMode to basic after power_on_reset (no carrier for 10ms) - } - } else { - carrier_count = 0; - } - } else { - //detect if carrier is still available - if(duration <= 0.75 * period || 1.25 * period <= duration) { - carrier_count--; - if(carrier_count <= carrier_threshold_off) { - carrier_on = false; - carrier_count = 0; - } - } else { - carrier_count = 0; - } - - if(carrier_on) { //if carrier is still available - //finetune the average period for reference - if(prevduration == duration) { - durationcounter++; - if(durationcounter == durationthreshold && period != duration) { - period = duration; - } - } else { - durationcounter = 0; - } - - //detect local MAX, and update maxCmd detection - if(duration > prevduration && duration > period) { - //rising edge above the normal periodduration: update localMax - localMax = duration; - localMaxIndex = periodcounter; - } else if( - duration < prevduration && localMax > period && - periodcounter <= localMaxIndex + 2) { - //we passed a local maximum, derive length between current and prev max index - if(miniMaxDelta == 0) { - prevMaxDelta = maxDelta; - } - maxDelta = (localMaxIndex - lastMaxIndex) * prescaler; - - //check if there's a miniDelta and if it should be added to previous or next delta - if(miniMaxDelta > 0) { - //by default minidelta is added to next - //in case doing this does not result in 2 consequtive valid deltas, but adding it to previous does result in 2 consequtive valid deltas, then assign it to previous delta - if((lfrfid_hitag_worker_validate_ripple_delta( - prevMaxDelta + miniMaxDelta) + - lfrfid_hitag_worker_validate_ripple_delta( - maxDelta - miniMaxDelta)) > - (lfrfid_hitag_worker_validate_ripple_delta(prevMaxDelta) + - lfrfid_hitag_worker_validate_ripple_delta(maxDelta))) { - //update the previous (last) bit from 0 to 1 if a command was ongoing - if(maxBitCounter > 0) { - maxBits[(maxBitCounter - 1) / 32] |= - (one >> (maxBitCounter - 1) % 32); - } - maxDelta -= miniMaxDelta; - lastMaxIndex = miniMaxIndex; - } - miniMaxDelta = 0; - } - - //now process the (possibly updated) delta - if(maxDelta >= 18) { - if(18 <= maxDelta && maxDelta <= 22) { - maxBitCounter++; - } else if(26 <= maxDelta && maxDelta <= 32) { - maxBits[(maxBitCounter) / 32] |= - (one >> (maxBitCounter) % 32); - maxBitCounter++; - } else if(maxBitCounter > 0) { - //end of bitstring caused by invalid delta - - if(lfrfid_hitag_worker_validate_bits( - maxBits, maxBitCounter)) { - cmd = 1; - cmdIndex = localMaxIndex; - - //CMD found, stop searching for minCMD: reset the minCmd detection - minBits[0] = 0; - minBits[1] = 0; - minBitCounter = 0; - } else { - //reset the maxCmd detection - maxBits[0] = 0; - maxBits[1] = 0; - maxBitCounter = 0; - } - } - lastMaxIndex = localMaxIndex; - } else { - miniMaxDelta = maxDelta; - miniMaxIndex = localMaxIndex; - } - localMax = 0; - } - - //terminate bitstring ico absence of next delta (a true end) - if(periodcounter == lastMaxIndex + 40 / prescaler && - maxBitCounter > 0) { - if(lfrfid_hitag_worker_validate_bits(maxBits, maxBitCounter)) { - cmd = 1; - cmdIndex = localMaxIndex; - - //CMD found, stop searching for minCMD: reset the minCmd detection - minBits[0] = 0; - minBits[1] = 0; - minBitCounter = 0; - } else { - //reset the maxCmd detection - maxBits[0] = 0; - maxBits[1] = 0; - maxBitCounter = 0; - } - } - - //detect local MIN, and update minCmd detection - if(duration < prevduration && duration < period) { - //rising edge above the normal periodduration: update localMax - localMin = duration; - localMinIndex = periodcounter; - } else if( - duration > prevduration && localMin < period && - periodcounter <= localMinIndex + 2) { - //we passed a local minimum, derive length between current and prev max index - if(miniMinDelta == 0) { - prevMinDelta = minDelta; - } - minDelta = (localMinIndex - lastMinIndex) * prescaler; - - //check if there's a miniDelta and if it should be added to previous or next delta - if(miniMinDelta > 0) { - //by default minidelta is added to next - //in case doing this does not result in 2 consequtive valid deltas, but adding it to previous does result in 2 consequtive valid deltas, then assign it to previous delta - if((lfrfid_hitag_worker_validate_ripple_delta( - prevMinDelta + miniMinDelta) + - lfrfid_hitag_worker_validate_ripple_delta( - minDelta - miniMinDelta)) > - (lfrfid_hitag_worker_validate_ripple_delta(prevMinDelta) + - lfrfid_hitag_worker_validate_ripple_delta(minDelta))) { - //update the previous (last) bit from 0 to 1 if a command was ongoing - if(minBitCounter > 0) { - minBits[(minBitCounter - 1) / 32] |= - (one >> (minBitCounter - 1) % 32); - } - minDelta -= miniMinDelta; - lastMinIndex = miniMinIndex; - } - miniMinDelta = 0; - } - - //now process the (possibly updated) delta - if(minDelta >= 18) { - if(18 <= minDelta && minDelta <= 22) { - minBitCounter++; - } else if(26 <= minDelta && minDelta <= 32) { - minBits[(minBitCounter) / 32] |= - (one >> (minBitCounter) % 32); - minBitCounter++; - } else if(minBitCounter > 0) { - //end of bitstring caused by invalid delta - if(lfrfid_hitag_worker_validate_bits( - minBits, minBitCounter)) { - cmd = -1; - cmdIndex = localMinIndex; - - //CMD found, stop searching for maxCMD: reset the maxCmd detection - maxBits[0] = 0; - maxBits[1] = 0; - maxBitCounter = 0; - } else { - //reset the minCmd detection - minBits[0] = 0; - minBits[1] = 0; - minBitCounter = 0; - } - } - lastMinIndex = localMinIndex; - } else { - miniMinDelta = minDelta; - miniMinIndex = localMinIndex; - } - localMin = 255; - } - - //terminate of bitstring caused by absence of next delta (a true end) - if(periodcounter == lastMinIndex + 40 / prescaler && - minBitCounter > 0) { - if(lfrfid_hitag_worker_validate_bits(minBits, minBitCounter)) { - cmd = -1; - cmdIndex = localMinIndex; - - //CMD found, stop searching for maxCMD: reset the maxCmd detection - maxBits[0] = 0; - maxBits[1] = 0; - maxBitCounter = 0; - } else { - //reset the minCmd detection - minBits[0] = 0; - minBits[1] = 0; - minBitCounter = 0; - } - } - - //if a valid cmd has been detected: process it & reply - if(cmd != 0) { - uint32_t bits[5]; - uint8_t size = 0; - //select the cmd bit source (max or min detection) - if(cmd == 1) { - bits[0] = maxBits[0]; - bits[1] = maxBits[1]; - size = maxBitCounter; - - maxBits[0] = 0; - maxBits[1] = 0; - maxBitCounter = 0; - } else { - bits[0] = minBits[0]; - bits[1] = minBits[1]; - size = minBitCounter; - - minBits[0] = 0; - minBits[1] = 0; - minBitCounter = 0; - } - cmd = 0; - - dmaLen = 0; - if(size == 5 && (bits[0] >> (32 - 5)) == 0b11001) { //SET_CCNEW - if(tagState != HitagStateQuiet) { - //reset selected state - tagState = HitagStateIdle; - - //set tag in advanced mode - tagMode = HitagModeAdvanced; - - //prepare emulate DMA buffer with advanced SN reply (page 0) - dataReply->replyvalue[0] = 0b00000111; - dataReply->replytype[0] = HITAG_STARTBIT3; - dataReply->replyvalue[1] = worker->tag->pageData[0]; - dataReply->replytype[1] = HITAG_BYTE; - dataReply->replyvalue[2] = worker->tag->pageData[1]; - dataReply->replytype[2] = HITAG_BYTE; - dataReply->replyvalue[3] = worker->tag->pageData[2]; - dataReply->replytype[3] = HITAG_BYTE; - dataReply->replyvalue[4] = worker->tag->pageData[3]; - dataReply->replytype[4] = HITAG_BYTE; - - dataReply->replylength = 5; - - dmaLen = lfrfid_hitag_worker_yield_dma_buffer_AC( - dataReply, dataDMA, worker->carrierPrescaler); - } - } else if(size == 5 && (bits[0] >> (32 - 5)) == 0b00110) { //SET_CC - if(tagState != HitagStateQuiet) { - //reset selected state - tagState = HitagStateIdle; - - //do not (re)set tag in basic mode, this only happens during power on reset - - //prepare emulate DMA buffer with basic SN reply (page 0) - dataReply->replyvalue[0] = 0b00000001; - dataReply->replytype[0] = HITAG_STARTBIT; - dataReply->replyvalue[1] = worker->tag->pageData[0]; - dataReply->replytype[1] = HITAG_BYTE; - dataReply->replyvalue[2] = worker->tag->pageData[1]; - dataReply->replytype[2] = HITAG_BYTE; - dataReply->replyvalue[3] = worker->tag->pageData[2]; - dataReply->replytype[3] = HITAG_BYTE; - dataReply->replyvalue[4] = worker->tag->pageData[3]; - dataReply->replytype[4] = HITAG_BYTE; - - dataReply->replylength = 5; - - dmaLen = lfrfid_hitag_worker_yield_dma_buffer_AC( - dataReply, dataDMA, worker->carrierPrescaler); - } - } else if(size == 45 && (bits[0] >> (32 - 5)) == 0b00000) { //SELECT - //check if SN is matching - uint32_t SN_cmd = bits[0] << 5 | bits[1] >> (32 - 5); - uint32_t SN_tag = worker->tag->pageData[0] << 24 | - worker->tag->pageData[1] << 16 | - worker->tag->pageData[2] << 8 | - worker->tag->pageData[3]; - if(SN_cmd == SN_tag) { - //reply with config (page 1 from tag memory) - tagState = HitagStateSelected; - - //prepare emulate DMA buffer with basic/advanced config page reply (page 1) - if(tagMode == HitagModeBasic) { - dataReply->replyvalue[0] = 0b00000001; - dataReply->replytype[0] = HITAG_STARTBIT; - - dataReply->replyvalue[1] = - worker->tag->pageData[4 + 0]; - dataReply->replytype[1] = HITAG_BYTE; - dataReply->replyvalue[2] = - worker->tag->pageData[4 + 1]; - dataReply->replytype[2] = HITAG_BYTE; - dataReply->replyvalue[3] = - worker->tag->pageData[4 + 2]; - dataReply->replytype[3] = HITAG_BYTE; - dataReply->replyvalue[4] = - worker->tag->pageData[4 + 3]; - dataReply->replytype[4] = HITAG_BYTE; - - dataReply->replylength = 5; - } else if(tagMode == HitagModeAdvanced) { - dataReply->replyvalue[0] = 0b00111111; - dataReply->replytype[0] = HITAG_STARTBIT6; - - dataReply->replyvalue[1] = - worker->tag->pageData[4 + 0]; - dataReply->replytype[1] = HITAG_BYTE; - dataReply->replyvalue[2] = - worker->tag->pageData[4 + 1]; - dataReply->replytype[2] = HITAG_BYTE; - dataReply->replyvalue[3] = - worker->tag->pageData[4 + 2]; - dataReply->replytype[3] = HITAG_BYTE; - dataReply->replyvalue[4] = - worker->tag->pageData[4 + 3]; - dataReply->replytype[4] = HITAG_BYTE; - - uint8_t crc = CRC_PRESET; - lfrfid_hitag_worker_calc_crc( - &crc, dataReply->replyvalue[1], 8); - lfrfid_hitag_worker_calc_crc( - &crc, dataReply->replyvalue[2], 8); - lfrfid_hitag_worker_calc_crc( - &crc, dataReply->replyvalue[3], 8); - lfrfid_hitag_worker_calc_crc( - &crc, dataReply->replyvalue[4], 8); - - dataReply->replyvalue[5] = crc; - dataReply->replytype[5] = HITAG_CRC; - - dataReply->replylength = 6; - } - - dmaLen = lfrfid_hitag_worker_yield_dma_buffer_MC( - dataReply, dataDMA, worker->carrierPrescaler); - } - } else if(size == 20 && (bits[0] >> (31))) { //R/W - if(tagState == HitagStateSelected) { - //reply with requested pages in case of read public (ignore secret block/pages & ignore write cmd) - uint8_t rwCmd = bits[0] >> (32 - 4); - uint8_t addr = bits[0] >> 20 & 0xff; - uint8_t pages = 0; - switch(rwCmd) { - case(0b1100): - //read public page - if(worker->tag->pageKnown[addr] && - worker->tag->pagePublic[addr]) { - pages = 1; - } - break; - case(0b1101): - //read public block - pages = (64 - addr) % 4; - if(pages == 0) { - pages = 4; - } - for(uint8_t p = addr; p < addr + pages; p++) { - if(!worker->tag->pageKnown[p] || - !worker->tag->pagePublic[p]) { - pages = 0; - break; - } - } - break; - } - if(pages > 0) { - //pages - for(uint8_t i = 0; i < pages; i++) { - dataReply->replyvalue[1 + i * 4 + 0] = - worker->tag->pageData[(addr + i) * 4 + 0]; - dataReply->replytype[1 + i * 4 + 0] = HITAG_BYTE; - dataReply->replyvalue[1 + i * 4 + 1] = - worker->tag->pageData[(addr + i) * 4 + 1]; - dataReply->replytype[1 + i * 4 + 1] = HITAG_BYTE; - dataReply->replyvalue[1 + i * 4 + 2] = - worker->tag->pageData[(addr + i) * 4 + 2]; - dataReply->replytype[1 + i * 4 + 2] = HITAG_BYTE; - dataReply->replyvalue[1 + i * 4 + 3] = - worker->tag->pageData[(addr + i) * 4 + 3]; - dataReply->replytype[1 + i * 4 + 3] = HITAG_BYTE; - } - //startsequence & CRC - if(tagMode == HitagModeBasic) { - dataReply->replyvalue[0] = 0b00000001; - dataReply->replytype[0] = HITAG_STARTBIT; - - dataReply->replylength = 1 + pages * 4; - } else if(tagMode == HitagModeAdvanced) { - dataReply->replyvalue[0] = 0b00111111; - dataReply->replytype[0] = HITAG_STARTBIT6; - - uint8_t crc = CRC_PRESET; - - for(uint8_t i = 0; i < pages; i++) { - lfrfid_hitag_worker_calc_crc( - &crc, - dataReply->replyvalue[1 + i * 4 + 0], - 8); - lfrfid_hitag_worker_calc_crc( - &crc, - dataReply->replyvalue[1 + i * 4 + 1], - 8); - lfrfid_hitag_worker_calc_crc( - &crc, - dataReply->replyvalue[1 + i * 4 + 2], - 8); - lfrfid_hitag_worker_calc_crc( - &crc, - dataReply->replyvalue[1 + i * 4 + 3], - 8); - } - - dataReply->replyvalue[1 + pages * 4] = crc; - dataReply->replytype[1 + pages * 4] = HITAG_CRC; - - dataReply->replylength = 2 + pages * 4; - } - - dmaLen = lfrfid_hitag_worker_yield_dma_buffer_MC( - dataReply, dataDMA, worker->carrierPrescaler); - } - } - } else if(size == 20 && (bits[0] >> (32 - 4)) == 0b0111) { //HALT - //TODO validate if dummy address is a valid one (one of the default public pages (32-63)) - - //acknowledge & go silent untill next reset - if(tagState == HitagStateSelected) { - tagState = HitagStateQuiet; - - if(tagMode == HitagModeBasic) { - dataReply->replyvalue[0] = 0b00000101; - dataReply->replytype[0] = HITAG_ACK; - } else if(tagMode == HitagModeAdvanced) { - dataReply->replyvalue[0] = 0b11111101; - dataReply->replytype[0] = HITAG_ACK_ADV; - } - - dataReply->replylength = 1; - - dmaLen = lfrfid_hitag_worker_yield_dma_buffer_MC( - dataReply, dataDMA, worker->carrierPrescaler); - } - } - - if(dmaLen > 0) { //switch to reply mode - //switch carrier mode to ETR required for emulation - lfrfid_hitag_worker_carrier_in_ETR_mode(capData, prescaler); - - //respect hitag WAIT1 time - uint32_t wait1_periods = 180 / prescaler; - while(capData->capCounter - cmdIndex < wait1_periods) { - furi_delay_us(40); - } - - //just start the DMA, stopping is handled in DMA transfer complete interrupt - lfrfid_hitag_worker_pull_out_dma_start((size_t)dmaLen); - } - } - } - } - } - } - - //reset buffer - buffer_reset(buffer); - } - - if(buffer_stream_get_overrun_count(capData->stream) > 0) { - FURI_LOG_E(TAG, "Read overrun, recovering"); - buffer_stream_reset(capData->stream); - } - - uint32_t flags = furi_event_flag_get(worker->events); - if(FURI_BIT(flags, LFRFIDHitagWorkerSignalStop)) { - break; - } - } - - //stop carrier capture in - lfrfid_hitag_worker_carrier_in_stop(); - - free(worker->tag); - free(dataDMA); - free(dataReply); - varint_pair_free(capData->pair); - buffer_stream_free(capData->stream); - free(capData); - - //clear the stop flag on workder so that i can use the worker to launch a new read routine without having to restart the app - furi_event_flag_clear(worker->events, 1 << LFRFIDHitagWorkerSignalStop); - - return 0; -} - -static int32_t lfrfid_hitag_worker_read_thread(void* thread_context) { - LFRFIDHitagWorker* worker = (LFRFIDHitagWorker*)thread_context; - - //initialize a tag datastructure - worker->tag = malloc(sizeof(LFRFIDHitag)); - lfrfid_hitag_worker_initialize_tag_data(worker->tag); - - //prep variables for decoder - LfRfidHitagProtocol currentProtocol = HitagProtocolAntiCollision; - uint8_t startBits = 1; - uint32_t memBlock[HITAG_BLOCKPAGES] = {0}; - uint8_t bits = 0; - bool halfBit = false; - bool finished = false; - - //prep variables for worker - uint8_t expectedBits = 33; - uint8_t readings = 0; - uint8_t currPage = 0; - bool readSucces = false; - uint8_t readThreshold = 3; - - // setup pins for reading - furi_hal_rfid_pins_read(); - - //and setup TIM1 CH1N with DMA - //fill the DMA arr & ccr buffers - /*start with looping SETCC command*/ - LfRfidHitagCMDData* dataCMD = malloc(sizeof(LfRfidHitagCMDData)); - dataCMD->CMDlength = 0; - dataCMD->CMDposition = 0; - dataCMD->CMDsubposition = 0; - dataCMD->CMDactive = false; - - LfRfidHitagCMDData* backupCMD = malloc(sizeof(LfRfidHitagCMDData)); - uint32_t value[] = { - 128, - 0b00110, - 1, - 213, - (uint32_t)(1.05 * 33 * HITAG_BITPERIODS_AC)}; //always take 5% reading time buffer - uint8_t type[] = {HITAG_ON, HITAG_SET, HITAG_STOP, HITAG_ON, HITAG_ON}; - for(size_t i = 0; i < 5; i++) { - backupCMD->CMDvalue[i] = value[i]; - backupCMD->CMDtype[i] = type[i]; - } - backupCMD->CMDlength = 5; - backupCMD->CMDposition = 0; - backupCMD->CMDsubposition = 0; - backupCMD->CMDloop = - true; //not really required since by definition the backup cmd is looping already in yield code - backupCMD->CMDactive = true; - - TimerDMAData* dataDMA = malloc(sizeof(TimerDMAData)); - lfrfid_hitag_worker_yield_dma_buffer_BPLM(dataCMD, backupCMD, dataDMA, 0, DMA_BUFFER_SIZE / 2); - lfrfid_hitag_worker_yield_dma_buffer_BPLM( - dataCMD, backupCMD, dataDMA, DMA_BUFFER_SIZE / 2, DMA_BUFFER_SIZE / 2); - - //start TIM1 with DMA function - lfrfid_hitag_worker_carrier_out_start( - dataDMA->timer_buffer_arr, dataDMA->timer_buffer_ccr, DMA_BUFFER_SIZE, worker); - - // start capture - LfRfidHitagCaptureData* capData = malloc(sizeof(LfRfidHitagCaptureData)); - capData->stream = buffer_stream_alloc(READ_BUFFER_SIZE, READ_BUFFER_COUNT); - capData->pair = varint_pair_alloc(); - furi_hal_rfid_tim_read_capture_start(lfrfid_hitag_worker_capture_in_cc_isr, capData); - - while(1) { - Buffer* buffer = buffer_stream_receive(capData->stream, 1); - - //process data if available - if(buffer != NULL) { - size_t buffsize = buffer_get_size(buffer); - uint8_t* buffdata = buffer_get_data(buffer); - - //if scanning for tagdata, then also parse buffer data - size_t index = 0; - while(expectedBits > 0 && index < buffsize) { - uint32_t duration; - uint32_t pulse; - size_t tmp_size; - - if(!varint_pair_unpack( - &buffdata[index], buffsize - index, &pulse, &duration, &tmp_size)) { - FURI_LOG_E(TAG, "can't unpack varint pair"); - break; - } else { - index += tmp_size; - - //feed pulse duration to decoder - lfrfid_hitag_worker_decoder_feed( - pulse, - duration, - memBlock, - &bits, - &halfBit, - &finished, - currentProtocol, - startBits); - - //see if i have a valid bit sequence - if(finished) { - //bitsequence ready for validation - if(bits == - expectedBits) { //eligable bitsequence detected, accept it only after x consequtive readings - //reset the backupCMD count to ensure some pause between bitsequence detection & the CMD injection - backupCMD->CMDcount = 0; - - if(readings == 0) { - for(uint8_t p = 0; p < (expectedBits - 1) / 32; p++) { - worker->tag->pageData[(currPage + p) * 4 + 0] = - memBlock[p] >> 24 & 0xff; - worker->tag->pageData[(currPage + p) * 4 + 1] = - memBlock[p] >> 16 & 0xff; - worker->tag->pageData[(currPage + p) * 4 + 2] = - memBlock[p] >> 8 & 0xff; - worker->tag->pageData[(currPage + p) * 4 + 3] = memBlock[p] & - 0xff; - worker->tag->pageKnown[currPage + p] = 0; - } - readings = 1; - } else if(readings < readThreshold) { - bool match = true; - for(uint8_t p = 0; p < (expectedBits - 1) / 32; p++) { - if(worker->tag->pageData[(currPage + p) * 4 + 0] != - (memBlock[p] >> 24 & 0xff) || - worker->tag->pageData[(currPage + p) * 4 + 1] != - (memBlock[p] >> 16 & 0xff) || - worker->tag->pageData[(currPage + p) * 4 + 2] != - (memBlock[p] >> 8 & 0xff) || - worker->tag->pageData[(currPage + p) * 4 + 3] != - (memBlock[p] & 0xff)) { - match = false; - break; - } - } - if(match) { - readings++; - if(readings == readThreshold) { - //succesful page(s) reading - readSucces = true; - - for(uint8_t p = 0; p < (expectedBits - 1) / 32; p++) { - worker->tag->pageKnown[currPage + p] = 1; - } - - readings = 0; - expectedBits = 0; //to prevent further updates to tag data - } - } else { - for(uint8_t p = 0; p < (expectedBits - 1) / 32; p++) { - worker->tag->pageData[(currPage + p) * 4 + 0] = - memBlock[p] >> 24 & 0xff; - worker->tag->pageData[(currPage + p) * 4 + 1] = - memBlock[p] >> 16 & 0xff; - worker->tag->pageData[(currPage + p) * 4 + 2] = - memBlock[p] >> 8 & 0xff; - worker->tag->pageData[(currPage + p) * 4 + 3] = - memBlock[p] & 0xff; - worker->tag->pageKnown[currPage + p] = 0; - } - readings = 1; - } - } - } - //reset tempdata for next bitsequence readout - memset(memBlock, 0, HITAG_BLOCKPAGES * sizeof(memBlock[0])); - bits = 0; - finished = false; - } - } - } - - //reset buffer - buffer_reset(buffer); - } - - //if bitsequence found & after pause, prep & launch the command for reading out next bit of data - if(readSucces && backupCMD->CMDcount >= 7) { - readSucces = false; - backupCMD->CMDcount = - 0; //required since otherwise the 'relaunch' of cmd is triggered straightaway - - //update protocolData - size_t data_size = protocol_dict_get_data_size(worker->dict, LFRFIDProtocolHitag1); - lfrfid_hitag_worker_combine_tag_data(worker->tag); - protocol_dict_set_data( - worker->dict, LFRFIDProtocolHitag1, worker->tag->tagData, data_size); - - if(currPage == 0) { //read config page next via SELECT cmd - FURI_LOG_D( - TAG, - "Hitag1, [%02X %02X %02X %02X] found", - worker->tag->pageData[0], - worker->tag->pageData[1], - worker->tag->pageData[2], - worker->tag->pageData[3]); - DOLPHIN_DEED(DolphinDeedRfidReadSuccess); - worker->workerstatus = LFRFIDHitagStatusDetected; - - currentProtocol = HitagProtocolManchesterCoding; - expectedBits = 33; - currPage = 1; - } else if(currPage == 1) { //process config page and start reading public blocks - lfrfid_hitag_worker_process_config_page(worker->tag); - FURI_LOG_D( - TAG, - "Hitag1, [%02X %02X %02X %02X] config page read", - worker->tag->pageData[0], - worker->tag->pageData[1], - worker->tag->pageData[2], - worker->tag->pageData[3]); - //find next public readible block - for(currPage = 4; currPage <= HITAG_PAGES - 4; currPage += 4) { - if(worker->tag->pagePublic[currPage] && - (worker->tag->pageRW[currPage] & READ)) { //public readible page/block found - expectedBits = 129; - break; - } else { - FURI_LOG_D( - TAG, - "Hitag1, [%02X %02X %02X %02X] block %u not public/readible", - worker->tag->pageData[0], - worker->tag->pageData[1], - worker->tag->pageData[2], - worker->tag->pageData[3], - currPage / 4); - } - } - } else if(currPage < HITAG_PAGES - 4) { //scan for next public readible page - FURI_LOG_D( - TAG, - "Hitag1, [%02X %02X %02X %02X] block %u read", - worker->tag->pageData[0], - worker->tag->pageData[1], - worker->tag->pageData[2], - worker->tag->pageData[3], - currPage / 4); - for(currPage += 4; currPage <= HITAG_PAGES - 4; currPage += 4) { - if(worker->tag->pagePublic[currPage] && - (worker->tag->pageRW[currPage] & READ)) { //public readible page/block found - expectedBits = 129; - break; - } else { - FURI_LOG_D( - TAG, - "Hitag1, [%02X %02X %02X %02X] block %u not public/readible", - worker->tag->pageData[0], - worker->tag->pageData[1], - worker->tag->pageData[2], - worker->tag->pageData[3], - currPage / 4); - } - } - } else if( - currPage == - HITAG_PAGES - - 4) { //all public readible pages read: signal view & break out of while loop (stops the worker) - FURI_LOG_D( - TAG, - "Hitag1, [%02X %02X %02X %02X] block %u read", - worker->tag->pageData[0], - worker->tag->pageData[1], - worker->tag->pageData[2], - worker->tag->pageData[3], - currPage / 4); - - worker->workerstatus = LFRFIDHitagStatusRead; - break; - } - - if(expectedBits > 0) { //inject CMD - //prep SELECT cmd bytes - uint8_t SN0 = worker->tag->pageData[0]; - uint8_t SN1 = worker->tag->pageData[1]; - uint8_t SN2 = worker->tag->pageData[2]; - uint8_t SN3 = worker->tag->pageData[3]; - uint8_t crc = CRC_PRESET; - lfrfid_hitag_worker_calc_crc(&crc, (0b00000 << (8 - 5)), 5); - lfrfid_hitag_worker_calc_crc(&crc, SN0, 8); - lfrfid_hitag_worker_calc_crc(&crc, SN1, 8); - lfrfid_hitag_worker_calc_crc(&crc, SN2, 8); - lfrfid_hitag_worker_calc_crc(&crc, SN3, 8); - - //prep SELECT cmd array - uint32_t value[] = { - 128, - 0b00000, - SN0, - SN1, - SN2, - SN3, - crc, - 1, - 213, - (uint32_t)(1.05 * 33 * HITAG_BITPERIODS_MC)}; //always take 5% reading time buffer - uint8_t type[] = { - HITAG_ON, - HITAG_SELECT, - HITAG_BYTE, - HITAG_BYTE, - HITAG_BYTE, - HITAG_BYTE, - HITAG_CRC, - HITAG_STOP, - HITAG_ON, - HITAG_ON}; - uint16_t len = 10; - uint16_t cnt = 1; - for(size_t i = 0; i < cnt * len; i++) { - dataCMD->CMDvalue[i] = value[i % len]; - dataCMD->CMDtype[i] = type[i % len]; - } - dataCMD->CMDlength = cnt * len; - dataCMD->CMDposition = 0; - dataCMD->CMDsubposition = 0; - dataCMD->CMDloop = false; - - if(currPage < 4 * 4) { //add READPAGE cmd to array - crc = CRC_PRESET; - lfrfid_hitag_worker_calc_crc(&crc, (0b1100 << (8 - 4)), 4); - lfrfid_hitag_worker_calc_crc(&crc, currPage, 8); - uint32_t value2[] = { - 128, - 0b1100, - currPage, - crc, - 1, - 213, - (uint32_t)(1.05 * 33 * HITAG_BITPERIODS_MC)}; //always take 5% reading time buffer - uint8_t type2[] = { - HITAG_ON, HITAG_CMD, HITAG_BYTE, HITAG_CRC, HITAG_STOP, HITAG_ON, HITAG_ON}; - uint16_t len2 = 7; - uint16_t cnt2 = 5; - for(size_t i = 0; i < cnt2 * len2; i++) { - dataCMD->CMDvalue[cnt * len + i] = value2[i % len2]; - dataCMD->CMDtype[cnt * len + i] = type2[i % len2]; - } - dataCMD->CMDlength += cnt2 * len2; - } else { //add READBLOCK cmd to array - crc = CRC_PRESET; - lfrfid_hitag_worker_calc_crc(&crc, (0b1101 << (8 - 4)), 4); - lfrfid_hitag_worker_calc_crc(&crc, currPage, 8); - uint32_t value2[] = { - 128, - 0b1101, - currPage, - crc, - 1, - 213, - (uint32_t)(1.05 * 129 * HITAG_BITPERIODS_MC)}; //always take 5% reading time buffer - uint8_t type2[] = { - HITAG_ON, HITAG_CMD, HITAG_BYTE, HITAG_CRC, HITAG_STOP, HITAG_ON, HITAG_ON}; - uint16_t len2 = 7; - uint16_t cnt2 = 5; - for(size_t i = 0; i < cnt2 * len2; i++) { - dataCMD->CMDvalue[cnt * len + i] = value2[i % len2]; - dataCMD->CMDtype[cnt * len + i] = type2[i % len2]; - } - dataCMD->CMDlength += cnt2 * len2; - } - } - } - - //while not having x consequtive reads, relaunch the (already prepared) command - if(!readSucces && backupCMD->CMDcount >= 7 && currPage > 0 && expectedBits > 0) { - backupCMD->CMDcount = 0; - dataCMD->CMDposition = 0; - dataCMD->CMDsubposition = 0; - } - - if(buffer_stream_get_overrun_count(capData->stream) > 0) { - FURI_LOG_E(TAG, "Read overrun, recovering"); - buffer_stream_reset(capData->stream); - } - - //check for stop events - uint32_t flags = furi_event_flag_get(worker->events); - if(FURI_BIT(flags, LFRFIDHitagWorkerSignalStop)) { - break; - } - - //check for DMA HT/TC signals to repopulate the DMA buffer - if(worker->DMAeventCount > 1) { - FURI_LOG_E(TAG, "DMA refilling can't keep up %u", worker->DMAeventCount); - } - if(FURI_BIT(flags, LFRFIDHitagWorkerSignalHalfTransfer)) { - //refill first half of dma buffer - lfrfid_hitag_worker_yield_dma_buffer_BPLM( - dataCMD, backupCMD, dataDMA, 0, DMA_BUFFER_SIZE / 2); - } - if(FURI_BIT(flags, LFRFIDHitagWorkerSignalTransferComplete)) { - //refill second half of dma buffer - lfrfid_hitag_worker_yield_dma_buffer_BPLM( - dataCMD, backupCMD, dataDMA, DMA_BUFFER_SIZE / 2, DMA_BUFFER_SIZE / 2); - } - worker->DMAeventCount = 0; - //clear all flags (i have processed all known/expected events) - furi_event_flag_clear(worker->events, flags); - } - - //stop timers - furi_hal_rfid_tim_read_capture_stop(); - lfrfid_hitag_worker_carrier_out_stop(); - - //free memory - free(dataCMD); - free(backupCMD); - free(dataDMA); - varint_pair_free(capData->pair); - buffer_stream_free(capData->stream); - free(capData); - - //allow the view that started the thread to signal the stop, which will properly merge the threads & de-init the worker - if(currPage == HITAG_PAGES - 4) { - const uint32_t available_flags = (1 << LFRFIDHitagWorkerSignalStop); - while(true) { - uint32_t flags = furi_event_flag_wait( - worker->events, available_flags, FuriFlagWaitAny, FuriWaitForever); - - if(FURI_BIT(flags, LFRFIDHitagWorkerSignalStop)) { - break; - } - } - } - - free(worker->tag); - - //clear the stop flag on workder so that i can use the same worker to launch a new routine - furi_event_flag_clear(worker->events, 1 << LFRFIDHitagWorkerSignalStop); - - return 0; -} diff --git a/lib/lfrfid/lfrfid_hitag_worker.h b/lib/lfrfid/lfrfid_hitag_worker.h deleted file mode 100644 index 9817991b8..000000000 --- a/lib/lfrfid/lfrfid_hitag_worker.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -//#include -#include "protocols/lfrfid_protocols.h" - -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include - -#include - -#define HITAG_BLOCKS 16 -#define HITAG_BLOCKPAGES 4 -#define HITAG_PAGES 64 -#define HITAG_PAGEBYTES 4 - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - LFRFIDHitagWorkerSettingRead, - LFRFIDHitagWorkerSettingEmulate, -} LFRFIDHitagWorkerSetting; - -typedef enum { - LFRFIDHitagStatusScanning, - LFRFIDHitagStatusDetected, - LFRFIDHitagStatusRead, -} LFRFIDHitagStatus; - -typedef struct LFRFIDHitagWorker LFRFIDHitagWorker; - -/** - * @brief Get the tag read status - * - * @return tag read status - */ -LFRFIDHitagStatus lfrfid_hitag_worker_get_status(LFRFIDHitagWorker* worker); - -/** - * @brief Allocate a new LFRFIDHitagWorker instance - * - * @return LFRFIDHitagWorker* - */ -LFRFIDHitagWorker* lfrfid_hitag_worker_alloc(ProtocolDict* dict); - -/** - * @brief Free a LFRFIDHitagWorker instance - * - * @param worker LFRFIDHitagWorker instance - */ -void lfrfid_hitag_worker_free(LFRFIDHitagWorker* worker); - -/** - * @brief Start hitag worker from own generated field - * - * @param worker LFRFIDHitagWorker instance - * @param setting read/emulate - */ -void lfrfid_hitag_worker_start(LFRFIDHitagWorker* worker, LFRFIDHitagWorkerSetting setting); - -/** - * @brief Stop worker - * - * @param worker - */ -void lfrfid_hitag_worker_stop(LFRFIDHitagWorker* worker); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/lfrfid/lfrfid_worker.h b/lib/lfrfid/lfrfid_worker.h index 682b077a0..def9f89a4 100644 --- a/lib/lfrfid/lfrfid_worker.h +++ b/lib/lfrfid/lfrfid_worker.h @@ -23,7 +23,6 @@ typedef enum { LFRFIDWorkerReadTypeAuto, LFRFIDWorkerReadTypeASKOnly, LFRFIDWorkerReadTypePSKOnly, - LFRFIDWorkerReadTypeRTFOnly, } LFRFIDWorkerReadType; typedef enum { @@ -33,8 +32,6 @@ typedef enum { LFRFIDWorkerReadSenseCardEnd, LFRFIDWorkerReadStartASK, LFRFIDWorkerReadStartPSK, - LFRFIDWorkerReadStartRTF, - LFRFIDWorkerReadSenseHitag, //TODO combine with sense carstart? LFRFIDWorkerReadDone, } LFRFIDWorkerReadResult; diff --git a/lib/lfrfid/lfrfid_worker_i.h b/lib/lfrfid/lfrfid_worker_i.h index d46ef2bba..33c0bff08 100644 --- a/lib/lfrfid/lfrfid_worker_i.h +++ b/lib/lfrfid/lfrfid_worker_i.h @@ -8,7 +8,6 @@ #include #include "lfrfid_worker.h" #include "lfrfid_raw_worker.h" -#include "lfrfid_hitag_worker.h" #include "protocols/lfrfid_protocols.h" #ifdef __cplusplus diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index c716c0d09..9b6f16eb1 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -94,7 +94,7 @@ typedef enum { LFRFIDWorkerReadTimeout, } LFRFIDWorkerReadState; -static LFRFIDWorkerReadState lfrfid_worker_read_ttf( //tag talks first +static LFRFIDWorkerReadState lfrfid_worker_read_internal( LFRFIDWorker* worker, LFRFIDFeature feature, uint32_t timeout, @@ -334,63 +334,6 @@ static LFRFIDWorkerReadState lfrfid_worker_read_ttf( //tag talks first return state; } -static LFRFIDWorkerReadState lfrfid_worker_read_rtf( //reader talks first - LFRFIDWorker* worker, - LFRFIDFeature feature, - uint32_t timeout, - ProtocolId* result_protocol) { - UNUSED(feature); - LFRFIDWorkerReadState state = LFRFIDWorkerReadTimeout; - - FURI_LOG_D(TAG, "Start RTF"); - if(worker->read_cb) { - worker->read_cb(LFRFIDWorkerReadStartRTF, PROTOCOL_NO, worker->cb_ctx); - } - LFRFIDHitagWorker* hitag_worker = lfrfid_hitag_worker_alloc(worker->protocols); - - lfrfid_hitag_worker_start(hitag_worker, LFRFIDHitagWorkerSettingRead); - - FURI_LOG_D(TAG, "Read started"); - - //scan for hitag for a while and stay in hitag mode if card was detected - uint8_t delays = 0; - uint8_t delay_ms = 100; - bool notified = false; - while(1) { - furi_delay_ms(delay_ms); - - if(lfrfid_worker_check_for_stop(worker)) { - state = LFRFIDWorkerReadExit; - *result_protocol = PROTOCOL_NO; - break; - } - - if(lfrfid_hitag_worker_get_status(hitag_worker) == LFRFIDHitagStatusDetected) { - *result_protocol = - LFRFIDProtocolHitag1; //TODO get protocol ID from hitag_worker when expanding the worker to include other hitag protocols - if(!notified && worker->read_cb) { - worker->read_cb(LFRFIDWorkerReadSenseHitag, *result_protocol, worker->cb_ctx); - notified = true; - } - } else if(lfrfid_hitag_worker_get_status(hitag_worker) == LFRFIDHitagStatusRead) { - state = LFRFIDWorkerReadOK; - *result_protocol = - LFRFIDProtocolHitag1; //TODO get protocol ID from hitag_worker when expanding the worker to include other hitag protocols - break; - } else if(++delays >= timeout / delay_ms) { - state = LFRFIDWorkerReadTimeout; - break; - } - } - - lfrfid_hitag_worker_stop(hitag_worker); - lfrfid_hitag_worker_free(hitag_worker); - - FURI_LOG_D(TAG, "Read stopped"); - - return state; -} - static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { ProtocolId read_result = PROTOCOL_NO; LFRFIDWorkerReadState state; @@ -398,8 +341,6 @@ static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { if(worker->read_type == LFRFIDWorkerReadTypePSKOnly) { feature = LFRFIDFeaturePSK; - } else if(worker->read_type == LFRFIDWorkerReadTypeRTFOnly) { - feature = LFRFIDFeatureRTF; } else { feature = LFRFIDFeatureASK; } @@ -407,14 +348,8 @@ static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { if(worker->read_type == LFRFIDWorkerReadTypeAuto) { while(1) { // read for a while - - if(feature == LFRFIDFeatureASK || feature == LFRFIDFeaturePSK) { - state = lfrfid_worker_read_ttf( - worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); - } else if(feature == LFRFIDFeatureRTF) { - state = lfrfid_worker_read_rtf( - worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); - } + state = lfrfid_worker_read_internal( + worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); if(state == LFRFIDWorkerReadOK || state == LFRFIDWorkerReadExit) { break; @@ -423,9 +358,7 @@ static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { // switch to next feature if(feature == LFRFIDFeatureASK) { feature = LFRFIDFeaturePSK; - } else if(feature == LFRFIDFeaturePSK) { - feature = LFRFIDFeatureRTF; - } else if(feature == LFRFIDFeatureRTF) { + } else { feature = LFRFIDFeatureASK; } @@ -434,12 +367,10 @@ static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { } else { while(1) { if(worker->read_type == LFRFIDWorkerReadTypeASKOnly) { - state = lfrfid_worker_read_ttf(worker, feature, UINT32_MAX, &read_result); - } else if(worker->read_type == LFRFIDWorkerReadTypePSKOnly) { - state = lfrfid_worker_read_ttf( - worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); + state = lfrfid_worker_read_internal(worker, feature, UINT32_MAX, &read_result); } else { - state = lfrfid_worker_read_rtf(worker, feature, UINT32_MAX, &read_result); + state = lfrfid_worker_read_internal( + worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); } if(state == LFRFIDWorkerReadOK || state == LFRFIDWorkerReadExit) { @@ -475,7 +406,7 @@ static void lfrfid_worker_emulate_dma_isr(bool half, void* context) { furi_stream_buffer_send(stream, &flag, sizeof(uint32_t), 0); } -static void lfrfid_worker_emulate_ttf(LFRFIDWorker* worker) { +static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) { LFRFIDWorkerEmulateBuffer* buffer = malloc(sizeof(LFRFIDWorkerEmulateBuffer)); FuriStreamBuffer* stream = furi_stream_buffer_alloc(sizeof(uint32_t), sizeof(uint32_t)); LFRFIDProtocol protocol = worker->protocol; @@ -564,39 +495,6 @@ static void lfrfid_worker_emulate_ttf(LFRFIDWorker* worker) { pulse_glue_free(pulse_glue); } -static void lfrfid_worker_emulate_rtf(LFRFIDWorker* worker) { - LFRFIDHitagWorker* hitag_worker = lfrfid_hitag_worker_alloc( - worker->protocols); //todo, pass protocols & protocol id when expanding the worker to include other hitag protocols - - lfrfid_hitag_worker_start(hitag_worker, LFRFIDHitagWorkerSettingEmulate); - uint8_t delay_ms = 100; - while(1) { - furi_delay_ms(delay_ms); - - if(lfrfid_worker_check_for_stop(worker)) { - break; - } - } - - lfrfid_hitag_worker_stop(hitag_worker); - lfrfid_hitag_worker_free(hitag_worker); -} - -static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) { - if(worker != NULL) { - if(worker->protocols != NULL) { - LFRFIDFeature feature = - protocol_dict_get_features(worker->protocols, worker->protocol); - - if(feature == LFRFIDFeatureRTF) { - lfrfid_worker_emulate_rtf(worker); - } else { - lfrfid_worker_emulate_ttf(worker); - } - } - } -} - /**************************************************************************************************/ /********************************************* WRITE **********************************************/ /**************************************************************************************************/ @@ -623,7 +521,7 @@ static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) { t5577_write(&request->t5577); ProtocolId read_result = PROTOCOL_NO; - LFRFIDWorkerReadState state = lfrfid_worker_read_ttf( + LFRFIDWorkerReadState state = lfrfid_worker_read_internal( worker, protocol_dict_get_features(worker->protocols, protocol), LFRFID_WORKER_WRITE_VERIFY_TIME_MS, diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 43d59e123..2c1f0ad97 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -16,7 +16,6 @@ #include "protocol_pac_stanley.h" #include "protocol_keri.h" #include "protocol_gallagher.h" -#include "protocol_hitag1.h" const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, @@ -36,5 +35,4 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolPACStanley] = &protocol_pac_stanley, [LFRFIDProtocolKeri] = &protocol_keri, [LFRFIDProtocolGallagher] = &protocol_gallagher, - [LFRFIDProtocolHitag1] = &protocol_hitag1, }; \ No newline at end of file diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 6d0e3fa4e..848f003a3 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -5,7 +5,6 @@ typedef enum { LFRFIDFeatureASK = 1 << 0, /** ASK Demodulation */ LFRFIDFeaturePSK = 1 << 1, /** PSK Demodulation */ - LFRFIDFeatureRTF = 1 << 2, /** Reader Talks First: ASK Demodulation with 2 way communication */ } LFRFIDFeature; typedef enum { @@ -26,7 +25,6 @@ typedef enum { LFRFIDProtocolPACStanley, LFRFIDProtocolKeri, LFRFIDProtocolGallagher, - LFRFIDProtocolHitag1, LFRFIDProtocolMax, } LFRFIDProtocol; diff --git a/lib/lfrfid/protocols/protocol_hitag1.c b/lib/lfrfid/protocols/protocol_hitag1.c deleted file mode 100644 index 1890ea1ad..000000000 --- a/lib/lfrfid/protocols/protocol_hitag1.c +++ /dev/null @@ -1,104 +0,0 @@ -#include -#include -#include "lfrfid_protocols.h" - -#define HITAG1_PAGES 64 -#define HITAG1_DATA_SIZE HITAG1_PAGES * 4 + HITAG1_PAGES - -typedef struct { - uint8_t tagData[HITAG1_DATA_SIZE]; -} ProtocolHitag1; - -ProtocolHitag1* protocol_hitag1_alloc(void) { - ProtocolHitag1* protocol = malloc(sizeof(ProtocolHitag1)); - - return protocol; -}; - -void protocol_hitag1_free(ProtocolHitag1* protocol) { - free(protocol); -}; - -uint8_t* protocol_hitag1_get_data(ProtocolHitag1* protocol) { - return protocol->tagData; -}; - -void protocol_hitag1_decoder_start(ProtocolHitag1* protocol) { - UNUSED(protocol); - // Not applicalble, encoding & decoding is handled in lfrfid_hitag_worker... -}; - -bool protocol_hitag1_decoder_feed(ProtocolHitag1* protocol, bool level, uint32_t duration) { - UNUSED(protocol); - UNUSED(level); - UNUSED(duration); - // Not applicalble, encoding & decoding is handled in lfrfid_hitag_worker... - - bool result = false; - return result; -}; - -bool protocol_hitag1_encoder_start(ProtocolHitag1* protocol) { - UNUSED(protocol); - // Not applicalble, encoding & decoding is handled in lfrfid_hitag_worker... - - return false; -}; - -LevelDuration protocol_hitag1_encoder_yield(ProtocolHitag1* protocol) { - UNUSED(protocol); - // Not applicalble, encoding & decoding is handled in lfrfid_hitag_worker... - - bool level = 0; - uint32_t duration = 0; - return level_duration_make(level, duration); -}; - -bool protocol_hitag1_write_data(ProtocolHitag1* protocol, void* data) { - UNUSED(protocol); - UNUSED(data); - - //this protocol cannot be simply written to card --> don't do anything, just return false - - return false; -}; - -void protocol_hitag1_render_data(ProtocolHitag1* protocol, FuriString* result) { - uint8_t pages = 0; - for(uint8_t p = 0; p < HITAG1_PAGES; p++) { - pages += protocol->tagData[HITAG1_PAGES * 4 + p]; - } - furi_string_printf( - result, - "SN: %02X %02X %02X %02X\r\n" - "Pages read: %u / 64", - protocol->tagData[0], - protocol->tagData[1], - protocol->tagData[2], - protocol->tagData[3], - pages); -}; - -const ProtocolBase protocol_hitag1 = { - .name = "Hitag1", - .manufacturer = "Philips", - .data_size = HITAG1_DATA_SIZE, - .features = LFRFIDFeatureRTF, - .validate_count = 1, - .alloc = (ProtocolAlloc)protocol_hitag1_alloc, - .free = (ProtocolFree)protocol_hitag1_free, - .get_data = (ProtocolGetData)protocol_hitag1_get_data, - .decoder = - { - .start = (ProtocolDecoderStart)protocol_hitag1_decoder_start, - .feed = (ProtocolDecoderFeed)protocol_hitag1_decoder_feed, - }, - .encoder = - { - .start = (ProtocolEncoderStart)protocol_hitag1_encoder_start, - .yield = (ProtocolEncoderYield)protocol_hitag1_encoder_yield, - }, - .render_data = (ProtocolRenderData)protocol_hitag1_render_data, - .render_brief_data = (ProtocolRenderData)protocol_hitag1_render_data, - .write_data = (ProtocolWriteData)protocol_hitag1_write_data, -}; diff --git a/lib/lfrfid/protocols/protocol_hitag1.h b/lib/lfrfid/protocols/protocol_hitag1.h deleted file mode 100644 index 517eeb5ef..000000000 --- a/lib/lfrfid/protocols/protocol_hitag1.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once -#include - -extern const ProtocolBase protocol_hitag1; \ No newline at end of file From 27028471bb2a7b1a96f162c0ec37ed14cbeec292 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 23:16:25 +0300 Subject: [PATCH 19/40] Update changelog --- CHANGELOG.md | 1 - applications/main/application.fam | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cface529..2b016121a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,6 @@ * Infrared: `External output` move out of debug and add power option for external modules * Extra pack: Some app fixes * FBT: Fix vscode example config for debug builds - please run `./fbt vscode_dist` again if you had issues with debug builds size -* OFW PR 2658: Hitag (by @blackvault88) (with minor fixes, hitag excluded from add manually temporarily, + fixed lfrfid favourites start when key cannot be parsed) * OFW PR 2316: NFC V support (by @g3gg0 & @nvx) * OFW PR 2669: nfc: Fix MFUL tearing flags read (by @GMMan) * OFW PR 2666: BadUSB: Add fr-FR-mac key layout (by @FelixLgr) diff --git a/applications/main/application.fam b/applications/main/application.fam index 35a63ddab..4ac735e92 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -24,9 +24,9 @@ App( name="Basic applications for main menu", apptype=FlipperAppType.METAPACKAGE, provides=[ - "gpio", + #"gpio", #"ibutton", - "infrared", + #"infrared", "lfrfid", "nfc", "subghz", From ce7517d5ce090dc9d2ae8016e9b18c6799bcf112 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 17 May 2023 23:23:37 +0300 Subject: [PATCH 20/40] Update docs --- documentation/SubGHzRemoteProg.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/documentation/SubGHzRemoteProg.md b/documentation/SubGHzRemoteProg.md index a5357fe18..7cd8ea68a 100644 --- a/documentation/SubGHzRemoteProg.md +++ b/documentation/SubGHzRemoteProg.md @@ -68,6 +68,11 @@ Watch this videos to learn more (videos in Russian language): https://www.youtub 6. Long press (Right Arrow) - (0xF button - Btn:F) on Flipper for like 3-5 sec 7. Done? +## CAME Atomo + +1. Use google to find instructions - `how to program new CAME Atomo remote into receiver` +2. Watch this video to learn more (video in Russian language): https://www.youtube.com/watch?v=XeHUwfcSS30 + ## Nice Flor S - Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> Nice FloR-S 433Mhz From 15da25759e92b4252c4cddb0e3b3ef2686a9a82b Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Wed, 17 May 2023 23:30:22 +0100 Subject: [PATCH 21/40] Updated tv.ir Added Brandt TV Raw data Support (Thanks FAYVIXE for the codes and testing!) --- assets/resources/infrared/assets/tv.ir | 42 ++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index 8c781eea9..ee20cd342 100755 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 2nd May, 2023 -# Last Checked 2nd May, 2023 +# Last Updated 17th May, 2023 +# Last Checked 17th May, 2023 # name: POWER type: parsed @@ -1973,3 +1973,41 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 273 1816 272 767 277 789 255 785 249 791 253 787 246 1818 281 785 248 765 279 1812 276 789 255 759 275 791 253 1812 276 789 255 46372 281 1808 280 785 249 791 253 787 247 793 251 1814 274 791 253 1812 276 1814 274 791 253 1812 276 1814 274 1815 273 792 252 1813 275 42172 272 1819 280 785 249 765 279 761 273 768 276 764 280 1811 277 788 246 768 276 1815 273 792 252 788 246 794 250 1815 273 791 253 +# +# Brandt TV +# +name: POWER +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9346 4516 657 502 659 502 688 473 687 475 634 527 635 527 634 1662 633 528 662 1634 662 1658 635 1660 608 1687 609 1687 633 1662 634 527 634 1662 634 528 633 1663 632 529 632 530 631 1665 630 531 630 531 631 531 630 1666 630 531 630 1666 630 1666 630 532 630 1666 630 1666 630 1666 630 +# +name: VOL+ +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9348 4542 631 529 632 503 659 502 688 474 634 527 634 527 634 1661 635 528 661 1636 660 1659 634 1637 632 1687 608 1687 609 1687 609 552 632 1664 633 528 633 1663 633 529 632 1664 631 1665 631 531 630 531 631 531 631 1665 631 531 631 1666 630 531 631 531 630 1666 630 1666 630 1666 631 +# +name: VOL- +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9376 4512 660 475 684 475 635 527 634 528 633 527 634 527 634 1662 662 525 636 1658 637 1658 637 1658 636 1659 636 1660 635 1660 635 526 635 1661 634 527 634 1662 633 1663 632 1664 631 1665 630 531 631 531 630 531 630 1666 630 531 630 531 631 531 630 531 631 1666 630 1666 630 1667 630 +# +name: CH+ +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9315 4513 657 501 660 501 659 500 661 502 688 499 661 474 634 1660 634 527 634 1660 634 1686 608 1687 607 1686 608 1686 608 1686 608 552 608 1686 608 1687 607 1687 632 530 631 1664 630 1665 630 531 630 531 630 531 630 531 630 531 630 1665 630 531 630 531 630 1665 630 1665 630 1665 630 +# +name: CH- +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9345 4540 631 503 658 529 661 475 683 476 635 526 635 527 634 1661 634 527 662 1658 636 1659 634 1660 608 1686 609 1686 634 1661 634 527 634 1661 634 1662 633 1662 633 1663 632 1664 630 1665 630 531 630 531 630 531 630 531 631 531 630 531 630 531 630 532 629 1666 630 1666 629 1666 629 +# +name: MUTE +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9348 4516 657 501 660 502 659 502 659 502 688 474 634 527 634 1662 634 528 633 1687 634 1660 608 1664 631 1687 608 1687 609 1686 609 552 609 1687 608 553 632 529 633 529 632 530 631 1665 630 531 630 531 631 531 630 1666 630 1666 630 1666 630 1666 630 531 631 1666 630 1666 630 1666 630 From f8c4b6ba59943510d8af8ff9a3773dc61a2a6559 Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Wed, 17 May 2023 23:30:43 +0100 Subject: [PATCH 22/40] Updated projectors.ir No new additions --- assets/resources/infrared/assets/projectors.ir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/resources/infrared/assets/projectors.ir b/assets/resources/infrared/assets/projectors.ir index 8e81a25bd..43c3723ef 100644 --- a/assets/resources/infrared/assets/projectors.ir +++ b/assets/resources/infrared/assets/projectors.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 # Last Updated 2nd May, 2023 -# Last Checked 2nd May, 2023 +# Last Checked 17th May, 2023 # # ON name: POWER From f214f582695263e34596a58d3cd3de00983aec5e Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Wed, 17 May 2023 23:31:10 +0100 Subject: [PATCH 23/40] Updated fans.ir New additions --- assets/resources/infrared/assets/fans.ir | 34 ++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/assets/resources/infrared/assets/fans.ir b/assets/resources/infrared/assets/fans.ir index e422bdce4..a741daf8e 100644 --- a/assets/resources/infrared/assets/fans.ir +++ b/assets/resources/infrared/assets/fans.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 2nd May, 2023 -# Last Checked 2nd May, 2023 +# Last Updated 17th May, 2023 +# Last Checked 17th May, 2023 # name: POWER type: raw @@ -1503,3 +1503,33 @@ type: parsed protocol: NEC address: 80 00 00 00 command: 1A 00 00 00 +# +name: POWER +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1370 314 1375 320 519 1167 1370 322 1339 350 465 1221 467 1222 467 1222 466 1221 467 1221 467 1221 1317 7067 1317 372 1341 349 491 1198 1340 352 1337 353 486 1202 486 1226 462 1226 462 1227 461 1227 462 1227 1311 7073 1312 378 1311 378 462 1227 1311 378 1312 378 462 1227 462 1227 461 1227 462 1227 461 1227 461 1227 1311 7074 1311 378 1311 378 462 1227 1311 379 1310 379 461 1228 461 1228 460 1228 460 1228 460 1229 459 1228 1310 7076 1309 381 1309 380 460 1229 1310 381 1309 381 458 1230 458 1230 459 1230 459 1230 459 1230 458 1230 1309 7077 1309 380 1310 380 460 1229 1310 380 1310 380 459 1229 460 1229 459 1229 460 1229 459 1229 459 1229 1310 7075 1310 379 1310 380 459 1229 1310 380 1310 379 460 1229 460 1229 459 1229 460 1228 460 1229 459 1229 1310 7074 1310 380 1310 379 460 1229 1310 379 1310 379 460 1229 459 1229 459 1229 460 1229 460 1229 460 1228 1311 +# +name: SPEED+ +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1394 321 1369 321 520 1141 1397 321 1368 321 519 1142 495 1194 495 1194 494 1195 494 1195 1366 351 491 7891 1341 350 1337 353 487 1201 1337 354 1336 354 487 1202 487 1202 487 1202 487 1202 487 1202 1336 354 487 7900 1336 354 1337 354 486 1202 1337 354 1336 354 487 1202 487 1202 487 1203 486 1202 487 1202 1336 354 487 7901 1335 354 1336 354 487 1203 1335 354 1336 355 486 1203 486 1203 486 1203 486 1203 486 1203 1335 355 486 7900 1335 355 1335 355 486 1203 1335 355 1335 355 486 1203 486 1204 485 1204 485 1204 485 1203 1335 356 485 7900 1334 356 1334 356 485 1204 1334 356 1334 356 485 1204 485 1204 484 1204 485 1204 485 1204 1333 357 484 7901 1333 357 1333 380 460 1228 1310 380 1310 380 460 1228 460 1228 460 1228 460 1228 460 1228 1310 380 460 7924 1309 380 1310 380 460 1229 1309 380 1310 380 460 1229 459 1229 460 1229 459 1230 459 1230 1309 381 458 +# +name: ROTATE +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1343 372 1318 372 467 1194 1346 372 1318 372 467 1193 525 1164 1400 319 495 1166 523 1166 523 1167 521 7890 1344 347 1340 350 489 1200 1339 351 1339 351 488 1201 488 1201 1338 351 488 1201 488 1201 488 1201 488 7899 1338 352 1338 352 487 1201 1339 352 1338 352 488 1201 488 1201 1339 352 487 1202 487 1201 488 1201 488 7899 1338 352 1338 352 487 1201 1338 352 1338 352 487 1202 487 1202 1337 353 486 1202 487 1202 487 1202 487 7900 1337 353 1337 353 486 1202 1338 353 1337 353 486 1202 487 1202 1338 353 486 1202 487 1202 487 1202 487 7900 1337 353 1337 353 486 1203 1336 354 1336 354 485 1203 486 1204 1335 354 485 1203 485 1203 486 1203 486 7901 1336 378 1312 355 484 1205 1335 378 1312 378 461 1227 462 1227 1313 378 461 1228 461 1228 461 1228 461 7925 1312 378 1312 378 461 1228 1311 378 1312 378 461 1228 461 1228 1312 378 461 1228 461 1228 461 1228 461 +# +name: TIMER +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1370 319 1371 321 519 1167 1371 322 1366 324 488 1198 466 1222 466 1222 1317 375 464 1221 467 1220 468 7915 1317 372 1341 350 490 1197 1340 376 1313 377 462 1226 462 1226 462 1226 1312 377 462 1226 462 1226 462 7921 1312 377 1313 377 462 1226 1312 377 1312 377 462 1226 462 1226 462 1226 1312 378 462 1226 462 1226 462 7922 1312 377 1313 377 462 1226 1313 377 1312 377 462 1226 462 1226 462 1226 1313 377 462 1226 462 1226 462 7921 1312 377 1312 377 462 1226 1312 377 1313 377 462 1226 462 1226 462 1226 1312 377 462 1226 462 1226 462 7921 1312 377 1313 377 462 1226 1313 377 1312 354 485 1202 486 1202 486 1202 1337 352 487 1202 487 1201 487 7897 1337 352 1337 351 488 1200 1339 351 1339 352 487 1201 487 1201 488 1201 1338 352 487 1201 487 1201 488 7896 1338 352 1337 352 487 1201 1337 352 1337 352 487 1201 487 1201 487 1201 1338 352 487 1201 487 1201 487 +# +name: MODE +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1369 349 1370 319 521 1167 1371 321 1340 349 466 1221 467 1222 467 1221 468 1221 1318 373 466 1220 468 7917 1344 347 1341 349 489 1198 1340 351 1338 352 487 1202 486 1202 486 1202 486 1202 1337 353 486 1202 486 7922 1312 377 1313 377 462 1227 1312 377 1313 378 461 1227 461 1227 462 1227 462 1227 1312 377 462 1227 461 7923 1312 378 1311 378 461 1227 1312 378 1311 377 462 1227 461 1227 461 1227 461 1227 1312 377 462 1227 462 7922 1312 378 1312 377 462 1227 1312 354 1336 354 485 1227 461 1227 461 1227 461 1203 1336 353 486 1203 486 7922 1312 353 1336 354 485 1203 1336 354 1336 354 485 1203 485 1227 461 1227 461 1227 1312 378 461 1227 461 7923 1312 378 1312 378 461 1227 1312 378 1312 378 461 1228 461 1228 461 1228 460 1228 1311 378 461 1227 461 7923 1311 378 1311 378 461 1227 1312 378 1312 378 461 1227 462 1227 461 1227 461 1227 1312 378 461 1228 461 6641 1312 378 1312 355 484 1228 1311 355 1335 378 461 1228 461 1228 460 1228 460 1228 1311 378 461 1228 460 7924 1311 379 1310 379 460 1228 1311 379 1311 379 460 1229 459 1229 459 1229 460 1229 1310 380 459 1229 459 7925 1310 380 1309 381 458 1230 1309 381 1309 381 458 1230 458 1231 457 1231 458 1231 1308 381 458 1231 457 7952 1283 407 1283 407 432 1256 1283 407 1283 407 432 1257 431 1257 432 1257 431 1257 1283 408 431 1257 431 From 9c3d2d736d47d3f8d5b9a1ed36bb80a06f4e4dcb Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Wed, 17 May 2023 23:31:39 +0100 Subject: [PATCH 24/40] Updated audio.ir No new additions --- assets/resources/infrared/assets/audio.ir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/resources/infrared/assets/audio.ir b/assets/resources/infrared/assets/audio.ir index ce6d0a7aa..14c18c04c 100644 --- a/assets/resources/infrared/assets/audio.ir +++ b/assets/resources/infrared/assets/audio.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 # Last Updated 14th Apr, 2023 -# Last Checked 2nd May, 2023 +# Last Checked 17th May, 2023 # name: POWER type: parsed From 0164e061a44513354b0379497e87dabf175af3a6 Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Wed, 17 May 2023 23:32:00 +0100 Subject: [PATCH 25/40] Updated ac.ir No new additions --- assets/resources/infrared/assets/ac.ir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 777d5399c..9a272af0e 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 # Last Updated 2nd May, 2023 -# Last Checked 2nd May, 2023 +# Last Checked 17th May, 2023 # name: POWER type: raw From 5b4ec05dbe42dd073fdb0de3cf9dd1dcd57cbe38 Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Wed, 17 May 2023 23:47:04 +0100 Subject: [PATCH 26/40] Updated tv.ir More additions --- assets/resources/infrared/assets/tv.ir | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index ee20cd342..a0fe73bd9 100755 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -2011,3 +2011,39 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 9348 4516 657 501 660 502 659 502 659 502 688 474 634 527 634 1662 634 528 633 1687 634 1660 608 1664 631 1687 608 1687 609 1686 609 552 609 1687 608 553 632 529 633 529 632 530 631 1665 630 531 630 531 631 531 630 1666 630 1666 630 1666 630 1666 630 531 631 1666 630 1666 630 1666 630 +# +name: POWER +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 0C 00 00 00 +# +name: VOL+ +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 14 00 00 00 +# +name: VOL- +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 15 00 00 00 +# +name: CH+ +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 12 00 00 00 +# +name: CH- +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 13 00 00 00 +# +name: MUTE +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 0D 00 00 00 From 78a2b12be099868b3efff0ffa0f1af6c5662e7d6 Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Wed, 17 May 2023 23:50:24 +0100 Subject: [PATCH 27/40] Updated ac.ir New additions --- assets/resources/infrared/assets/ac.ir | 50 +++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 9a272af0e..44f685bce 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -1,6 +1,6 @@ Filetype: IR library file Version: 1 -# Last Updated 2nd May, 2023 +# Last Updated 17th May, 2023 # Last Checked 17th May, 2023 # name: POWER @@ -1770,3 +1770,51 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 645 17766 3059 8884 533 461 557 1427 560 435 584 433 534 458 534 458 534 458 588 404 587 405 586 1399 586 407 584 408 558 1429 555 1457 502 491 525 1460 528 1458 554 1431 554 1431 555 1431 554 438 555 438 554 439 553 440 553 464 528 464 528 464 529 465 528 465 526 467 526 467 527 465 553 439 554 439 554 438 554 439 554 439 553 439 554 439 553 439 553 439 554 440 552 464 528 464 528 465 528 466 527 466 501 492 525 468 526 466 527 466 553 439 554 439 553 440 552 1433 553 1433 553 2936 3024 8893 552 1458 527 465 528 465 527 467 526 467 500 493 524 468 526 467 526 467 552 1434 552 441 552 441 552 1435 550 465 528 1458 527 1458 528 1459 527 1485 501 1485 500 1486 501 491 527 466 528 465 528 465 527 465 528 465 527 465 528 465 528 465 527 465 528 465 527 466 527 466 527 492 500 492 501 492 500 493 500 493 500 492 526 466 527 465 527 465 527 465 527 466 527 466 527 466 527 465 527 466 526 466 527 466 526 467 526 493 499 493 474 518 499 494 500 493 526 2936 3025 8918 526 1459 527 466 527 466 527 466 526 466 527 466 526 467 526 467 525 467 526 1460 525 493 499 493 498 495 498 495 499 493 500 493 525 1460 527 1460 525 1460 526 1460 525 1460 526 1460 526 468 525 1487 499 1487 497 495 498 495 499 493 500 1486 525 1460 525 1460 525 467 525 467 526 467 525 467 526 468 524 1462 524 468 524 494 498 1488 497 1488 499 494 499 493 525 468 525 1461 524 468 525 468 524 468 525 468 525 468 524 468 525 469 524 494 498 494 499 1488 473 1514 524 +# +name: POWER +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 46 00 00 00 +# +name: TEMP+ +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 07 00 00 00 +# +name: TEMP- +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: TIMER +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 42 00 00 00 +# +name: MODE +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: POWER +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9464 4442 637 583 601 584 601 584 601 584 600 582 603 580 604 580 630 555 629 1637 627 1665 598 1668 571 1696 569 1696 594 1672 594 1672 592 1674 593 1673 593 591 594 1673 593 1672 593 591 591 593 569 616 568 616 593 593 591 1674 593 592 593 593 592 1675 592 1675 592 1700 568 1700 568 39559 9409 2272 567 96244 9461 2246 594 +# +name: SWING +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9505 4415 688 531 602 584 602 585 601 584 601 581 604 580 605 580 605 581 628 1636 628 1665 599 1667 597 1670 595 1671 595 1671 596 1672 595 1673 595 1673 595 592 594 591 595 1672 595 590 595 590 595 591 594 591 594 590 595 1672 595 1672 595 591 594 1672 595 1672 595 1672 595 1672 595 39565 9410 2244 592 96225 9442 2244 592 +# +name: TIMER +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9515 4477 575 611 576 551 636 610 576 611 575 610 576 581 606 580 606 580 606 1661 631 1639 628 1666 600 1667 599 1671 596 1673 595 1673 595 1673 595 593 594 617 570 594 593 1674 594 617 570 617 570 617 570 617 570 1699 569 1699 569 1699 569 618 569 1699 569 1698 570 1698 570 1699 569 From 2b017664607bf00a4ab0d3682b956187ce35370d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 18 May 2023 02:38:45 +0300 Subject: [PATCH 28/40] Mifare mini with sak 89 support --- lib/nfc/protocols/mifare_classic.c | 5 +++-- lib/nfc/protocols/mifare_common.c | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index d2d7467dc..e9d43baf5 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -376,7 +376,8 @@ bool mf_classic_is_value_block(MfClassicData* data, uint8_t block_num) { bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { UNUSED(ATQA1); - if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { + if((ATQA0 == 0x44 || ATQA0 == 0x04) && + (SAK == 0x08 || SAK == 0x88 || SAK == 0x09 || SAK == 0x89)) { return true; } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { //skylanders support @@ -393,7 +394,7 @@ MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t if((ATQA0 == 0x44 || ATQA0 == 0x04)) { if((SAK == 0x08 || SAK == 0x88)) { return MfClassicType1k; - } else if(SAK == 0x09) { + } else if((SAK == 0x09 || SAK == 0x89)) { return MfClassicTypeMini; } } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { diff --git a/lib/nfc/protocols/mifare_common.c b/lib/nfc/protocols/mifare_common.c index 90b57e1f0..5277cc8cd 100644 --- a/lib/nfc/protocols/mifare_common.c +++ b/lib/nfc/protocols/mifare_common.c @@ -6,7 +6,8 @@ MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { type = MifareTypeUltralight; } else if( - ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) || + ((ATQA0 == 0x44 || ATQA0 == 0x04) && + (SAK == 0x08 || SAK == 0x88 || SAK == 0x09 || SAK == 0x89)) || ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || ((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01))) { type = MifareTypeClassic; From e3cf9a03068c374d93b765902b55a0c1ce03440d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 18 May 2023 02:40:10 +0300 Subject: [PATCH 29/40] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b016121a..a16fc8256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Plugins: WiFi Marauder -> Added sniff pmkid on selected aps from 0.10.4 update (by @clipboard1) * Plugins: SubGHz Bruteforcer -> Increase delay just a little bit to fix some cases when receiver will not get codes and decrease manual transmit delay * Plugins: UART Terminal -> Fix crashes on plugin load with RX connected +* NFC: Mifare mini with SAK 0x89 support * SubGHz: **CAME Atomo - Add manually support and custom buttons support** * SubGHz: Fix crashes when deleting signals using right arrow button in `Read` mode * SubGHz: Restore Rx indication after deletion after Memory is FULL (by @wosk | PR #464) @@ -16,6 +17,7 @@ * GUI: Text Input improvements, added cursor and ability to set minimal length (by @Willy-JL) * BT API: Functions that allow to change bt mac address and device broadcasted name (by @Willy-JL and XFW contributors) * Infrared: `External output` move out of debug and add power option for external modules +* Infrared: Updated universal remote assets (by @amec0e | PR #474) * Extra pack: Some app fixes * FBT: Fix vscode example config for debug builds - please run `./fbt vscode_dist` again if you had issues with debug builds size * OFW PR 2316: NFC V support (by @g3gg0 & @nvx) From 11d4ad1e233b54ce1807416b8fbacb077a7999d3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 18 May 2023 03:16:51 +0300 Subject: [PATCH 30/40] fix builds --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 7776bce6b..a03063528 100644 --- a/.drone.yml +++ b/.drone.yml @@ -85,7 +85,7 @@ steps: - zip -r artifacts-extra-apps/flipper-z-f7-update-${DRONE_TAG}e.zip artifacts-extra-apps/f7-update-${DRONE_TAG}e - zip -r artifacts-ofw-anims/flipper-z-f7-update-${DRONE_TAG}n.zip artifacts-ofw-anims/f7-update-${DRONE_TAG}n - zip -r artifacts-default/flipper-z-f7-update-${DRONE_TAG}.zip artifacts-default/f7-update-${DRONE_TAG} - - tar czpf artifacts-default/flipper-z-any-scripts-${DRONE_TAG}.tgz scripts debug + - tar czpf artifacts-default/flipper-z-any-scripts-${DRONE_TAG}.tgz scripts - rm -rf artifacts-extra-apps/f7-update-${DRONE_TAG} - rm -rf artifacts-ofw-anims/f7-update-${DRONE_TAG} - rm -rf artifacts-default/f7-update-${DRONE_TAG} From 64817ac2166b67282534546193020030e985aace Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Thu, 18 May 2023 10:59:08 +0300 Subject: [PATCH 31/40] Move dangerous_settings check --- applications/main/subghz/application.fam | 13 ++++++++- .../main/subghz/subghz_dangerous_freq.c | 23 ++++++++++++++++ .../targets/f7/furi_hal/furi_hal_subghz.c | 27 ++++++++----------- .../targets/f7/furi_hal/furi_hal_subghz.h | 5 ++-- .../targets/f7/furi_hal/furi_hal_subghz_i.h | 3 +++ 5 files changed, 52 insertions(+), 19 deletions(-) create mode 100644 applications/main/subghz/subghz_dangerous_freq.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_subghz_i.h diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index f0dc66e89..6cd28bd85 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -10,7 +10,10 @@ App( "cli", "dialogs", ], - provides=["subghz_start"], + provides=[ + "subghz_start", + "subghz_load_dangerous_settings", + ], icon="A_Sub1ghz_14", stack_size=3 * 1024, order=10, @@ -23,3 +26,11 @@ App( requires=["subghz"], order=40, ) + +App( + appid="subghz_load_dangerous_settings", + apptype=FlipperAppType.STARTUP, + entry_point="subghz_dangerous_freq", + requires=["subghz"], + order=41, +) diff --git a/applications/main/subghz/subghz_dangerous_freq.c b/applications/main/subghz/subghz_dangerous_freq.c new file mode 100644 index 000000000..69a54f04b --- /dev/null +++ b/applications/main/subghz/subghz_dangerous_freq.c @@ -0,0 +1,23 @@ +#include +#include + +#include + +#include + +void subghz_dangerous_freq() { + bool is_extended_i = false; + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + + if(flipper_format_file_open_existing(fff_data_file, "/ext/subghz/assets/dangerous_settings")) { + flipper_format_read_bool( + fff_data_file, "yes_i_want_to_destroy_my_flipper", &is_extended_i, 1); + } + + furi_hal_subghz_set_dangerous_frequency(is_extended_i); + + flipper_format_free(fff_data_file); + furi_record_close(RECORD_STORAGE); +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 6a566d471..06a7990fb 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -41,6 +41,7 @@ volatile FuriHalSubGhz furi_hal_subghz = { .rolling_counter_mult = 1, .ext_module_power_disabled = false, .timestamp_file_names = false, + .dangerous_frequency_i = false, }; void furi_hal_subghz_select_radio_type(SubGhzRadioType state) { @@ -96,6 +97,10 @@ bool furi_hal_subghz_get_timestamp_file_names(void) { return furi_hal_subghz.timestamp_file_names; } +void furi_hal_subghz_set_dangerous_frequency(bool state_i) { + furi_hal_subghz.dangerous_frequency_i = state_i; +} + void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) { furi_hal_subghz.async_mirror_pin = pin; } @@ -448,29 +453,19 @@ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value) { } bool furi_hal_subghz_is_tx_allowed(uint32_t value) { - bool is_extended = false; + bool allow_extended_for_int = furi_hal_subghz.dangerous_frequency_i; - // TODO: !!! Move file check to another place - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - - if(flipper_format_file_open_existing(fff_data_file, "/ext/subghz/assets/dangerous_settings")) { - flipper_format_read_bool( - fff_data_file, "yes_i_want_to_destroy_my_flipper", &is_extended, 1); - } - - flipper_format_free(fff_data_file); - furi_record_close(RECORD_STORAGE); - - if(!(value >= 299999755 && value <= 350000335) && // was increased from 348 to 350 + if(!(allow_extended_for_int) && + !(value >= 299999755 && value <= 350000335) && // was increased from 348 to 350 !(value >= 386999938 && value <= 467750000) && // was increased from 464 to 467.75 - !(value >= 778999847 && value <= 928000000) && !(is_extended)) { + !(value >= 778999847 && value <= 928000000)) { FURI_LOG_I(TAG, "Frequency blocked - outside default range"); return false; } else if( + (allow_extended_for_int) && // !(value >= 281000000 && value <= 361000000) && !(value >= 378000000 && value <= 481000000) && - !(value >= 749000000 && value <= 962000000) && is_extended) { + !(value >= 749000000 && value <= 962000000)) { FURI_LOG_I(TAG, "Frequency blocked - outside dangerous range"); return false; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.h b/firmware/targets/f7/furi_hal/furi_hal_subghz.h index 35eab4faf..0ef7bd90a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.h +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.h @@ -78,8 +78,9 @@ typedef struct { FuriHalSpiBusHandle* spi_bus_handle; const GpioPin* cc1101_g0_pin; uint8_t rolling_counter_mult; - bool ext_module_power_disabled; - bool timestamp_file_names; + bool ext_module_power_disabled : 1; + bool timestamp_file_names : 1; + bool dangerous_frequency_i : 1; } FuriHalSubGhz; extern volatile FuriHalSubGhz furi_hal_subghz; diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz_i.h b/firmware/targets/f7/furi_hal/furi_hal_subghz_i.h new file mode 100644 index 000000000..e7fe2602f --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz_i.h @@ -0,0 +1,3 @@ +#pragma once + +void furi_hal_subghz_set_dangerous_frequency(bool state_i); \ No newline at end of file From e8264dc6e73005a714145dfc667b28d1c57ba13b Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Thu, 18 May 2023 13:48:25 +0300 Subject: [PATCH 32/40] subghz_remote_app --- .../main/subghz_remote/application.fam | 7 +- .../helpers/subrem_custom_event.h | 18 + .../main/subghz_remote/helpers/subrem_types.h | 32 + .../main/subghz_remote/scenes/subrem_scene.c | 30 + .../main/subghz_remote/scenes/subrem_scene.h | 29 + .../scenes/subrem_scene_config.h | 3 + .../scenes/subrem_scene_openmapfile.c | 41 + .../scenes/subrem_scene_remote.c | 129 ++ .../subghz_remote/scenes/subrem_scene_start.c | 74 ++ .../main/subghz_remote/subghz_remote_app.c | 1151 ++--------------- .../main/subghz_remote/subghz_remote_app_i.c | 449 +++++++ .../main/subghz_remote/subghz_remote_app_i.h | 84 ++ .../main/subghz_remote/views/remote.c | 302 +++++ .../main/subghz_remote/views/remote.h | 38 + 14 files changed, 1338 insertions(+), 1049 deletions(-) create mode 100644 applications/main/subghz_remote/helpers/subrem_custom_event.h create mode 100644 applications/main/subghz_remote/helpers/subrem_types.h create mode 100644 applications/main/subghz_remote/scenes/subrem_scene.c create mode 100644 applications/main/subghz_remote/scenes/subrem_scene.h create mode 100644 applications/main/subghz_remote/scenes/subrem_scene_config.h create mode 100644 applications/main/subghz_remote/scenes/subrem_scene_openmapfile.c create mode 100644 applications/main/subghz_remote/scenes/subrem_scene_remote.c create mode 100644 applications/main/subghz_remote/scenes/subrem_scene_start.c create mode 100644 applications/main/subghz_remote/subghz_remote_app_i.c create mode 100644 applications/main/subghz_remote/subghz_remote_app_i.h create mode 100644 applications/main/subghz_remote/views/remote.c create mode 100644 applications/main/subghz_remote/views/remote.h diff --git a/applications/main/subghz_remote/application.fam b/applications/main/subghz_remote/application.fam index e09f8500f..e785043e6 100644 --- a/applications/main/subghz_remote/application.fam +++ b/applications/main/subghz_remote/application.fam @@ -3,7 +3,10 @@ App( name="Sub-GHz Remote", apptype=FlipperAppType.APP, entry_point="subghz_remote_app", - cdefines=["APP_SUBGHZREMOTE"], + cdefines=[ + "APP_SUBGHZREMOTE", + "SUBREM_LIGHT", + ], requires=[ "gui", "dialogs", @@ -11,4 +14,4 @@ App( icon="A_SubGHzRemote_14", stack_size=4 * 1024, order=11, -) +) \ No newline at end of file diff --git a/applications/main/subghz_remote/helpers/subrem_custom_event.h b/applications/main/subghz_remote/helpers/subrem_custom_event.h new file mode 100644 index 000000000..46ab8ad54 --- /dev/null +++ b/applications/main/subghz_remote/helpers/subrem_custom_event.h @@ -0,0 +1,18 @@ +#pragma once + +typedef enum { + //SubmenuIndex + SubmenuIndexSubRemOpenMapFile, + SubmenuIndexSubRemRemoteView, + SubmenuIndexSubRemAbout, + + //SubRemCustomEvent + SubRemCustomEventViewRemoteStartUP = 100, + SubRemCustomEventViewRemoteStartDOWN, + SubRemCustomEventViewRemoteStartLEFT, + SubRemCustomEventViewRemoteStartRIGHT, + SubRemCustomEventViewRemoteStartOK, + SubRemCustomEventViewRemoteBack, + SubRemCustomEventViewRemoteStop, + SubRemCustomEventViewRemoteForcedStop, +} SubRemCustomEvent; \ No newline at end of file diff --git a/applications/main/subghz_remote/helpers/subrem_types.h b/applications/main/subghz_remote/helpers/subrem_types.h new file mode 100644 index 000000000..1b99aac6d --- /dev/null +++ b/applications/main/subghz_remote/helpers/subrem_types.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +// TODO: File version/type logic +// #define SUBREM_APP_APP_FILE_VERSION 1 +// #define SUBREM_APP_APP_FILE_TYPE "Flipper SubRem Map file" +#define SUBREM_APP_EXTENSION ".txt" + +typedef enum { + SubRemSubKeyNameUp = (0U), + SubRemSubKeyNameDown, + SubRemSubKeyNameLeft, + SubRemSubKeyNameRight, + SubRemSubKeyNameOk, + SubRemSubKeyNameMaxCount, +} SubRemSubKeyName; + +typedef enum { + SubRemViewSubmenu, + SubRemViewWidget, + SubRemViewPopup, + SubRemViewTextInput, + SubRemViewIDRemote, +} SubRemViewID; + +typedef enum { + SubRemLoadMapStateBack = 0, + SubRemLoadMapStateError, + SubRemLoadMapStateOK, +} SubRemLoadMapState; \ No newline at end of file diff --git a/applications/main/subghz_remote/scenes/subrem_scene.c b/applications/main/subghz_remote/scenes/subrem_scene.c new file mode 100644 index 000000000..c45285b96 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene.c @@ -0,0 +1,30 @@ +#include "../subghz_remote_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const subrem_scene_on_enter_handlers[])(void*) = { +#include "subrem_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 subrem_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "subrem_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 subrem_scene_on_exit_handlers[])(void* context) = { +#include "subrem_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers subrem_scene_handlers = { + .on_enter_handlers = subrem_scene_on_enter_handlers, + .on_event_handlers = subrem_scene_on_event_handlers, + .on_exit_handlers = subrem_scene_on_exit_handlers, + .scene_num = SubRemSceneNum, +}; diff --git a/applications/main/subghz_remote/scenes/subrem_scene.h b/applications/main/subghz_remote/scenes/subrem_scene.h new file mode 100644 index 000000000..5c01f8ca5 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) SubRemScene##id, +typedef enum { +#include "subrem_scene_config.h" + SubRemSceneNum, +} SubRemScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers subrem_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "subrem_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 "subrem_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 "subrem_scene_config.h" +#undef ADD_SCENE diff --git a/applications/main/subghz_remote/scenes/subrem_scene_config.h b/applications/main/subghz_remote/scenes/subrem_scene_config.h new file mode 100644 index 000000000..93d4de642 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(subrem, start, Start) +ADD_SCENE(subrem, openmapfile, OpenMapFile) +ADD_SCENE(subrem, remote, Remote) \ No newline at end of file diff --git a/applications/main/subghz_remote/scenes/subrem_scene_openmapfile.c b/applications/main/subghz_remote/scenes/subrem_scene_openmapfile.c new file mode 100644 index 000000000..3391845e1 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene_openmapfile.c @@ -0,0 +1,41 @@ +#include "../subghz_remote_app_i.h" + +void subrem_scene_openmapfile_on_enter(void* context) { + SubGhzRemoteApp* app = context; + SubRemLoadMapState load_state = subrem_load_from_file(app); + + if(load_state == SubRemLoadMapStateError) { +#ifdef SUBREM_LIGHT + dialog_message_show_storage_error(app->dialogs, "Can't load\nMap file"); +#else + DialogMessage* message = dialog_message_alloc(); + + dialog_message_set_header(message, "Map File Error", 64, 8, AlignCenter, AlignCenter); + dialog_message_set_text(message, "Can't load\nMap file", 64, 32, AlignCenter, AlignCenter); + dialog_message_set_buttons(message, "Back", NULL, NULL); + dialog_message_show(app->dialogs, message); + + dialog_message_free(message); +#endif + } + if(load_state == SubRemLoadMapStateOK) { + scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); + } else { + // TODO: Map Preset Reset + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SubRemSceneStart)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + } +} + +bool subrem_scene_openmapfile_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void subrem_scene_openmapfile_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/main/subghz_remote/scenes/subrem_scene_remote.c b/applications/main/subghz_remote/scenes/subrem_scene_remote.c new file mode 100644 index 000000000..d543ca999 --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene_remote.c @@ -0,0 +1,129 @@ +#include "../subghz_remote_app_i.h" +#include "../views/remote.h" + +#include + +#define TAG "SubRemScenRemote" + +void subrem_scene_remote_callback(SubRemCustomEvent event, void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void subrem_scene_remote_raw_callback_end_tx(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SubRemCustomEventViewRemoteForcedStop); +} + +static uint8_t subrem_scene_remote_event_to_index(SubRemCustomEvent event_id) { + uint8_t ret = 0; + + if(event_id == SubRemCustomEventViewRemoteStartUP) { + ret = SubRemSubKeyNameUp; + } else if(event_id == SubRemCustomEventViewRemoteStartDOWN) { + ret = SubRemSubKeyNameDown; + } else if(event_id == SubRemCustomEventViewRemoteStartLEFT) { + ret = SubRemSubKeyNameLeft; + } else if(event_id == SubRemCustomEventViewRemoteStartRIGHT) { + ret = SubRemSubKeyNameRight; + } else if(event_id == SubRemCustomEventViewRemoteStartOK) { + ret = SubRemSubKeyNameOk; + } + + return ret; +} + +static bool subrem_scene_remote_update_data_show(void* context) { + SubGhzRemoteApp* app = context; + bool ret = false; + + subrem_view_remote_add_data_to_show( + app->subrem_remote_view, + furi_string_get_cstr(app->subs_preset[0]->label), + furi_string_get_cstr(app->subs_preset[1]->label), + furi_string_get_cstr(app->subs_preset[2]->label), + furi_string_get_cstr(app->subs_preset[3]->label), + furi_string_get_cstr(app->subs_preset[4]->label)); + + return ret; +} + +void subrem_scene_remote_on_enter(void* context) { + SubGhzRemoteApp* app = context; + + subrem_scene_remote_update_data_show(app); + + subrem_view_remote_set_callback(app->subrem_remote_view, subrem_scene_remote_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDRemote); +} + +bool subrem_scene_remote_on_event(void* context, SceneManagerEvent event) { + SubGhzRemoteApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubRemCustomEventViewRemoteBack) { + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SubRemSceneOpenMapFile)) { + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SubRemSceneStart)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + } + return true; + } else if( + event.event == SubRemCustomEventViewRemoteStartUP || + event.event == SubRemCustomEventViewRemoteStartDOWN || + event.event == SubRemCustomEventViewRemoteStartLEFT || + event.event == SubRemCustomEventViewRemoteStartRIGHT || + event.event == SubRemCustomEventViewRemoteStartOK) { + // Start sending sub + subrem_tx_stop_sub(app, true); + app->chusen_sub = subrem_scene_remote_event_to_index(event.event); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateLoading); + if(subrem_tx_start_sub( + app, + app->subs_preset[app->chusen_sub], + subrem_scene_remote_raw_callback_end_tx)) { + subrem_view_remote_set_presed_btn(app->subrem_remote_view, app->chusen_sub); + subrem_view_remote_set_state( + app->subrem_remote_view, SubRemViewRemoteStateSending); + notification_message(app->notifications, &sequence_blink_start_magenta); + } else { + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + } + return true; + } else if(event.event == SubRemCustomEventViewRemoteForcedStop) { + subrem_tx_stop_sub(app, true); + subrem_view_remote_set_presed_btn(app->subrem_remote_view, 0); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + + notification_message(app->notifications, &sequence_blink_stop); + return true; + } else if(event.event == SubRemCustomEventViewRemoteStop) { + if(subrem_tx_stop_sub(app, false)) { + subrem_view_remote_set_presed_btn(app->subrem_remote_view, 0); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + + notification_message(app->notifications, &sequence_blink_stop); + } + return true; + } + } + // } else if(event.type == SceneManagerEventTypeTick) { + // } + return false; +} + +void subrem_scene_remote_on_exit(void* context) { + SubGhzRemoteApp* app = context; + + subrem_tx_stop_sub(app, true); + + subrem_view_remote_set_presed_btn(app->subrem_remote_view, 0); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + + notification_message(app->notifications, &sequence_blink_stop); +} diff --git a/applications/main/subghz_remote/scenes/subrem_scene_start.c b/applications/main/subghz_remote/scenes/subrem_scene_start.c new file mode 100644 index 000000000..962eda54c --- /dev/null +++ b/applications/main/subghz_remote/scenes/subrem_scene_start.c @@ -0,0 +1,74 @@ +#include "../subghz_remote_app_i.h" +#include "../helpers/subrem_custom_event.h" + +void subrem_scene_start_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + SubGhzRemoteApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void subrem_scene_start_on_enter(void* context) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + Submenu* submenu = app->submenu; + submenu_add_item( + submenu, + "Open Map File", + SubmenuIndexSubRemOpenMapFile, + subrem_scene_start_submenu_callback, + app); +#if FURI_DEBUG + submenu_add_item( + submenu, + "Remote_Debug", + SubmenuIndexSubRemRemoteView, + subrem_scene_start_submenu_callback, + app); +#endif + // submenu_add_item( + // submenu, + // "About", + // SubmenuIndexSubGhzRemoteAbout, + // subrem_scene_start_submenu_callback, + // app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewSubmenu); +} + +bool subrem_scene_start_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSubRemOpenMapFile) { + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); + consumed = true; + } + // } else if(event.event == SubmenuIndexSubRemAbout) { + // scene_manager_next_scene(app->scene_manager, SubRemSceneAbout); + // consumed = true; + // } +#if FURI_DEBUG + else if(event.event == SubmenuIndexSubRemRemoteView) { + scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); + consumed = true; + } +#endif + } + + return consumed; +} + +void subrem_scene_start_on_exit(void* context) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/main/subghz_remote/subghz_remote_app.c b/applications/main/subghz_remote/subghz_remote_app.c index 8cef4da3a..de80c9cdd 100644 --- a/applications/main/subghz_remote/subghz_remote_app.c +++ b/applications/main/subghz_remote/subghz_remote_app.c @@ -1,766 +1,27 @@ -#include +#include "subghz_remote_app_i.h" -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include #include -#include -#include -#include -#include - -#define SUBREMOTEMAP_FOLDER "/ext/subghz_remote" -#define SUBREMOTEMAP_EXTENSION ".txt" - -#define TAG "SubGHzRemote" - -typedef struct { - uint32_t frequency; - FuriString* name; - - FuriString* protocol; - uint32_t repeat; - - uint8_t* data; - size_t data_size; - - SubGhzProtocolDecoderBase* decoder; -} SubRemotePreset; - -typedef struct { - FuriMutex* model_mutex; - - FuriMessageQueue* input_queue; - - ViewPort* view_port; - Gui* gui; - - SubGhzSetting* setting; - SubGhzEnvironment* environment; - SubGhzReceiver* subghz_receiver; - NotificationApp* notification; - SubRemotePreset* txpreset; - - FuriString* up_file; - FuriString* down_file; - FuriString* left_file; - FuriString* right_file; - FuriString* ok_file; - - FuriString* file_path; - - char* up_label; - char* down_label; - char* left_label; - char* right_label; - char* ok_label; - - int up_enabled; - int down_enabled; - int left_enabled; - int right_enabled; - int ok_enabled; - - char* send_status; - int send_status_c; - int processing; - - SubGhzTransmitter* tx_transmitter; - FlipperFormat* tx_fff_data; - const char* tx_file_path; - int button; - - int file_result; - bool tx_not_allowed; - - FuriString* signal; -} SubGHzRemote; - -SubRemotePreset* subghz_remote_preset_alloc(void) { - SubRemotePreset* preset = malloc(sizeof(SubRemotePreset)); - preset->name = furi_string_alloc(); - preset->protocol = furi_string_alloc(); - preset->repeat = 200; - return preset; +static bool subghz_remote_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + SubGhzRemoteApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); } -void subghz_remote_preset_free(SubRemotePreset* preset) { - furi_string_free(preset->name); - furi_string_free(preset->protocol); - free(preset); +static bool subghz_remote_app_back_event_callback(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); } -static char* char_to_str(char* str, int i) { - char* converted = malloc(sizeof(char) * i + 1); - memcpy(converted, str, i); - - converted[i] = '\0'; - - return converted; +static void subghz_remote_app_tick_event_callback(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); } -//get filename without path -static char* extract_filename(const char* name, int len) { - FuriString* tmp; - tmp = furi_string_alloc(); - - //remove path - path_extract_filename_no_ext(name, tmp); - - return char_to_str((char*)furi_string_get_cstr(tmp), len); -} - -static void cfg_read_file_path( - FlipperFormat* fff_file, - FuriString* text_file_path, - char** text_file_label, - const char* read_key, - int* is_enabled) { - if(!flipper_format_read_string(fff_file, read_key, text_file_path)) { - FURI_LOG_W(TAG, "Could not read %s string", read_key); - *text_file_label = "N/A"; - *is_enabled = 0; - } else { - *text_file_label = extract_filename(furi_string_get_cstr(text_file_path), 16); - //FURI_LOG_D(TAG, "%s file: %s", read_key, furi_string_get_cstr(text_file_path)); - *is_enabled = 1; - } - flipper_format_rewind(fff_file); -} - -static void cfg_read_file_label( - FlipperFormat* fff_file, - char** text_file_label, - const char* read_key, - bool is_enabled) { - FuriString* temp_label = furi_string_alloc(); - - if(!flipper_format_read_string(fff_file, read_key, temp_label)) { - FURI_LOG_W(TAG, "Could not read %s string", read_key); - } else { - if(is_enabled == 1) { - *text_file_label = char_to_str((char*)furi_string_get_cstr(temp_label), 16); - } - //FURI_LOG_D(TAG, "%s label: %s", read_key, *text_file_label); - } - flipper_format_rewind(fff_file); - furi_string_free(temp_label); -} - -/* - * check that map file exists - * assign variables to values within map file - * set missing filenames to N/A - * set filename as label if label definitions are missing - * set error flag if all buttons are N/A - * set error flag if missing map file - */ - -void subghz_remote_cfg_set_check(SubGHzRemote* app, FuriString* file_name) { - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - - app->file_result = 1; - - app->up_enabled = 0; - app->down_enabled = 0; - app->left_enabled = 0; - app->right_enabled = 0; - app->ok_enabled = 0; - - //check that map file exists - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_E(TAG, "Could not open MAP file %s", furi_string_get_cstr(file_name)); - } else { - //Filename Assignment/Check Start - - //assign variables to values within map file - //set missing filenames to N/A - cfg_read_file_path(fff_data_file, app->up_file, &app->up_label, "UP", &app->up_enabled); - - cfg_read_file_path( - fff_data_file, app->down_file, &app->down_label, "DOWN", &app->down_enabled); - - cfg_read_file_path( - fff_data_file, app->left_file, &app->left_label, "LEFT", &app->left_enabled); - - cfg_read_file_path( - fff_data_file, app->right_file, &app->right_label, "RIGHT", &app->right_enabled); - - cfg_read_file_path(fff_data_file, app->ok_file, &app->ok_label, "OK", &app->ok_enabled); - - //File definitions are done. - //File checks will follow after label assignment in order to close the universal_rf_map file without the need to reopen it again. - - //Label Assignment/Check Start - - cfg_read_file_label(fff_data_file, &app->up_label, "ULABEL", app->up_enabled); - cfg_read_file_label(fff_data_file, &app->down_label, "DLABEL", app->down_enabled); - cfg_read_file_label(fff_data_file, &app->left_label, "LLABEL", app->left_enabled); - cfg_read_file_label(fff_data_file, &app->right_label, "RLABEL", app->right_enabled); - cfg_read_file_label(fff_data_file, &app->ok_label, "OKLABEL", app->ok_enabled); - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - - //File Existence Check - //Check each file definition if not already set to "N/A" - - //determine if files exist. - //determine whether or not to continue to launch app with missing variables - //if 5 files are missing, throw error - - //if button is still enabled, check that file exists - if(app->up_enabled == 1) { - furi_string_set(file_name, app->up_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open UP file %s", furi_string_get_cstr(file_name)); - - //disable button, and set label to "N/A" - app->up_enabled = 0; - app->up_label = "N/A"; - } - - //close the file - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - if(app->down_enabled == 1) { - furi_string_set(file_name, app->down_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open DOWN file %s", furi_string_get_cstr(file_name)); - - app->down_enabled = 0; - app->down_label = "N/A"; - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - if(app->left_enabled == 1) { - furi_string_set(file_name, app->left_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open LEFT file %s", furi_string_get_cstr(file_name)); - - app->left_enabled = 0; - app->left_label = "N/A"; - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - if(app->right_enabled == 1) { - furi_string_set(file_name, app->right_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open RIGHT file %s", furi_string_get_cstr(file_name)); - - app->right_enabled = 0; - app->right_label = "N/A"; - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - if(app->ok_enabled == 1) { - furi_string_set(file_name, app->ok_file); - fff_data_file = flipper_format_file_alloc(storage); - - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_W(TAG, "Could not open OK file %s", furi_string_get_cstr(file_name)); - - app->ok_enabled = 0; - app->ok_label = "N/A"; - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - } - - furi_record_close(RECORD_STORAGE); - - if(app->up_enabled == 0 && app->down_enabled == 0 && app->left_enabled == 0 && - app->right_enabled == 0 && app->ok_enabled == 0) { - app->file_result = 1; - } else { - app->file_result = 2; - } -} - -static void subghz_remote_end_send(SubGHzRemote* app) { - app->processing = 0; -} - -bool subghz_remote_set_preset(SubRemotePreset* p, const char* preset) { - if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { - furi_string_set(p->name, "AM270"); - } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { - furi_string_set(p->name, "AM650"); - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { - furi_string_set(p->name, "FM238"); - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { - furi_string_set(p->name, "FM476"); - } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { - FURI_LOG_E(TAG, "Custom preset unsupported now"); - return false; - // furi_string_set(p->name, "CUSTOM"); - } else { - FURI_LOG_E(TAG, "Unsupported preset"); - return false; - } - return true; -} - -bool subghz_remote_key_load( - SubRemotePreset* preset, - FlipperFormat* fff_file, - FlipperFormat* fff_data, - SubGhzSetting* setting, - SubGhzReceiver* receiver, - const char* path) { - // - if(!flipper_format_rewind(fff_file)) { - FURI_LOG_E(TAG, "Rewind error"); - return false; - } - - FuriString* temp_str; - temp_str = furi_string_alloc(); - - bool res = false; - - subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK); - keeloq_reset_original_btn(); - subghz_custom_btns_reset(); - - do { - // load frequency from file - if(!flipper_format_read_uint32(fff_file, "Frequency", &preset->frequency, 1)) { - FURI_LOG_W(TAG, "Cannot read frequency. Defaulting to 433.92 MHz"); - preset->frequency = 433920000; - } - - // load preset from file - if(!flipper_format_read_string(fff_file, "Preset", temp_str)) { - FURI_LOG_W(TAG, "Could not read Preset. Defaulting to Ook650Async"); - furi_string_set(temp_str, "FuriHalSubGhzPresetOok650Async"); - } - if(!subghz_remote_set_preset(preset, furi_string_get_cstr(temp_str))) { - FURI_LOG_E(TAG, "Could not set preset"); - break; - } - if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { - // TODO: check if preset is custom - FURI_LOG_E(TAG, "Could not use custom preset"); - break; - } - size_t preset_index = - subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(preset->name)); - preset->data = subghz_setting_get_preset_data(setting, preset_index); - preset->data_size = subghz_setting_get_preset_data_size(setting, preset_index); - - // load protocol from file - if(!flipper_format_read_string(fff_file, "Protocol", preset->protocol)) { - FURI_LOG_E(TAG, "Could not read Protocol."); - break; - } - if(!flipper_format_rewind(fff_data)) { - FURI_LOG_E(TAG, "Rewind error"); - return false; - } - - if(!furi_string_cmp_str(preset->protocol, "RAW")) { - subghz_protocol_raw_gen_fff_data(fff_data, path); - // repeat - if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &preset->repeat, 1)) { - FURI_LOG_E(TAG, "Unable to insert or update Repeat"); - break; - } - - } else { - stream_copy_full( - flipper_format_get_raw_stream(fff_file), flipper_format_get_raw_stream(fff_data)); - // repeat - if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &preset->repeat, 1)) { - FURI_LOG_E(TAG, "Unable to insert or update Repeat"); - break; - } - } - - if(!flipper_format_rewind(fff_file)) { - FURI_LOG_E(TAG, "Rewind error"); - return false; - } - - preset->decoder = subghz_receiver_search_decoder_base_by_name( - receiver, furi_string_get_cstr(preset->protocol)); - if(preset->decoder) { - SubGhzProtocolStatus status = - subghz_protocol_decoder_base_deserialize(preset->decoder, fff_data); - if(status != SubGhzProtocolStatusOk) { - FURI_LOG_E(TAG, "Protocol deserialize failed, status = %d", status); - break; - } - } else { - FURI_LOG_E(TAG, "Protocol %s not found", furi_string_get_cstr(temp_str)); - } - - res = true; - } while(0); - - furi_string_free(temp_str); - - return res; -} - -// method modified from subghz_i.c -// https://github.com/flipperdevices/flipperzero-firmware/blob/b0daa601ad5b87427a45f9089c8b403a01f72c2a/applications/subghz/subghz_i.c#L417-L456 -bool subghz_remote_save_protocol_to_file(FlipperFormat* fff_file, const char* dev_file_name) { - furi_assert(fff_file); - furi_assert(dev_file_name); - - Storage* storage = furi_record_open(RECORD_STORAGE); - Stream* flipper_format_stream = flipper_format_get_raw_stream(fff_file); - - bool saved = false; - FuriString* file_dir; - file_dir = furi_string_alloc(); - - path_extract_dirname(dev_file_name, file_dir); - do { - if(!storage_simply_mkdir(storage, furi_string_get_cstr(file_dir))) { - FURI_LOG_E(TAG, "(save) Cannot mkdir"); - break; - } - - if(!storage_simply_remove(storage, dev_file_name)) { - FURI_LOG_E(TAG, "(save) Cannot remove"); - break; - } - - stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); - stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS); - - saved = true; - //FURI_LOG_D(TAG, "(save) OK Save"); - } while(0); - furi_string_free(file_dir); - furi_record_close(RECORD_STORAGE); - return saved; -} - -void subghz_remote_tx_stop(SubGHzRemote* app) { - if(app->processing == 0) { - return; - } - - if(!furi_string_cmp_str(app->txpreset->protocol, "RAW")) { - while(!furi_hal_subghz_is_async_tx_complete()) { - furi_delay_ms(15); - } - } - - //Stop TX - furi_hal_subghz_stop_async_tx(); - //FURI_LOG_I(TAG, "TX Done!"); - subghz_transmitter_stop(app->tx_transmitter); - - //FURI_LOG_D(TAG, "Checking if protocol is dynamic"); - const SubGhzProtocolRegistry* protocol_registry_items = - subghz_environment_get_protocol_registry(app->environment); - const SubGhzProtocol* proto = subghz_protocol_registry_get_by_name( - protocol_registry_items, furi_string_get_cstr(app->txpreset->protocol)); - //FURI_LOG_D(TAG, "Protocol-TYPE %d", proto->type); - - if(proto && proto->type == SubGhzProtocolTypeDynamic) { - //FURI_LOG_D(TAG, "Protocol is dynamic. Saving key"); - // Remove repeat if it was present - flipper_format_delete_key(app->tx_fff_data, "Repeat"); - - subghz_remote_save_protocol_to_file(app->tx_fff_data, app->tx_file_path); - - keeloq_reset_mfname(); - keeloq_reset_kl_type(); - keeloq_reset_original_btn(); - subghz_custom_btns_reset(); - star_line_reset_mfname(); - star_line_reset_kl_type(); - } - - subghz_transmitter_free(app->tx_transmitter); - furi_hal_subghz_idle(); - - notification_message(app->notification, &sequence_blink_stop); - - subghz_remote_preset_free(app->txpreset); - flipper_format_free(app->tx_fff_data); - subghz_remote_end_send(app); -} - -static bool subghz_remote_send_sub(SubGHzRemote* app, FlipperFormat* fff_data) { - // - bool res = false; - do { - if(!furi_hal_subghz_is_tx_allowed(app->txpreset->frequency)) { - FURI_LOG_E( - TAG, - "In your settings, only reception on this frequency (%lu) is allowed,\r\n" - "the actual operation of the subghz remote app is not possible\r\n ", - app->txpreset->frequency); - app->tx_not_allowed = true; - subghz_remote_end_send(app); - break; - } else { - app->tx_not_allowed = false; - } - - app->tx_transmitter = subghz_transmitter_alloc_init( - app->environment, furi_string_get_cstr(app->txpreset->protocol)); - if(!app->tx_transmitter) { - break; - } - - if(subghz_transmitter_deserialize(app->tx_transmitter, fff_data) != - SubGhzProtocolStatusOk) { - FURI_LOG_E(TAG, "Deserialize error!"); - break; - } - - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_custom_preset(app->txpreset->data); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); - - furi_hal_subghz_idle(); - - furi_hal_subghz_set_frequency_and_path(app->txpreset->frequency); - furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - - if(!furi_hal_subghz_tx()) { - FURI_LOG_E(TAG, "Sending not allowed"); - break; - } - - //FURI_LOG_I(TAG, "Sending..."); - notification_message(app->notification, &sequence_blink_start_magenta); - - furi_hal_subghz_start_async_tx(subghz_transmitter_yield, app->tx_transmitter); - - res = true; - } while(0); - - return res; -} - -static void subghz_remote_send_signal(SubGHzRemote* app, Storage* storage, const char* path) { - //FURI_LOG_D(TAG, "Sending: %s", path); - - app->tx_file_path = path; - - app->tx_fff_data = flipper_format_string_alloc(); - - app->txpreset = subghz_remote_preset_alloc(); - - // load settings/stream from .sub file - FlipperFormat* fff_file = flipper_format_file_alloc(storage); - bool open_ok = false; - do { - if(!flipper_format_file_open_existing(fff_file, path)) { - FURI_LOG_E(TAG, "Could not open file %s", path); - break; - } - if(!subghz_remote_key_load( - app->txpreset, - fff_file, - app->tx_fff_data, - app->setting, - app->subghz_receiver, - path)) { - FURI_LOG_E(TAG, "Could not load key"); - break; - } - open_ok = true; - } while(0); - flipper_format_free(fff_file); - if(!open_ok) { - FURI_LOG_E(TAG, "Could not load file!"); - return; - } - - subghz_remote_send_sub(app, app->tx_fff_data); -} - -static void subghz_remote_process_signal(SubGHzRemote* app, FuriString* signal) { - view_port_update(app->view_port); - - //FURI_LOG_D(TAG, "signal = %s", furi_string_get_cstr(signal)); - - if(strlen(furi_string_get_cstr(signal)) > 12) { - Storage* storage = furi_record_open(RECORD_STORAGE); - subghz_remote_send_signal(app, storage, furi_string_get_cstr(signal)); - furi_record_close(RECORD_STORAGE); - } else if(strlen(furi_string_get_cstr(signal)) < 10) { - subghz_remote_end_send(app); - } -} - -static void render_callback(Canvas* canvas, void* ctx) { - SubGHzRemote* app = ctx; - furi_check(furi_mutex_acquire(app->model_mutex, FuriWaitForever) == FuriStatusOk); - - //setup different canvas settings - if(app->file_result == 1) { - //if map has no valid filenames defined - canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 62, 5, AlignCenter, AlignTop, "Config is incorrect."); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignTop, "Please configure map."); - canvas_draw_str_aligned(canvas, 62, 60, AlignCenter, AlignBottom, "Press Back to Exit."); - } else if(app->file_result == 3) { - //if map has no valid filenames defined - canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 62, 5, AlignCenter, AlignTop, "Checking config."); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignTop, "If app is stuck..."); - canvas_draw_str_aligned(canvas, 62, 60, AlignCenter, AlignBottom, "Press Back to Exit."); - } else if(app->tx_not_allowed) { - canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 62, 5, AlignCenter, AlignTop, "Transmission is blocked."); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 62, 15, AlignCenter, AlignTop, "Frequency is outside of"); - canvas_draw_str_aligned(canvas, 62, 25, AlignCenter, AlignTop, "default range."); - canvas_draw_str_aligned(canvas, 62, 35, AlignCenter, AlignTop, "Check docs."); - canvas_draw_str_aligned(canvas, 62, 60, AlignCenter, AlignBottom, "Press Back to Exit."); - } else { - //map found, draw all the things - canvas_clear(canvas); - - //canvas_set_font(canvas, FontPrimary); - //canvas_draw_str(canvas, 0, 10, "U: "); - //canvas_draw_str(canvas, 0, 20, "L: "); - //canvas_draw_str(canvas, 0, 30, "R: "); - //canvas_draw_str(canvas, 0, 40, "D: "); - //canvas_draw_str(canvas, 0, 50, "Ok: "); - - //PNGs are located in assets/icons/SubGHzRemote before compilation - - //Icons for Labels - //canvas_draw_icon(canvas, 0, 0, &I_SubGHzRemote_LeftAlignedButtons_9x64); - canvas_draw_icon(canvas, 1, 5, &I_ButtonUp_7x4); - canvas_draw_icon(canvas, 1, 15, &I_ButtonDown_7x4); - canvas_draw_icon(canvas, 2, 23, &I_ButtonLeft_4x7); - canvas_draw_icon(canvas, 2, 33, &I_ButtonRight_4x7); - canvas_draw_icon(canvas, 0, 42, &I_Ok_btn_9x9); - canvas_draw_icon(canvas, 0, 53, &I_back_10px); - - //Labels - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 10, 10, app->up_label); - canvas_draw_str(canvas, 10, 20, app->down_label); - canvas_draw_str(canvas, 10, 30, app->left_label); - canvas_draw_str(canvas, 10, 40, app->right_label); - canvas_draw_str(canvas, 10, 50, app->ok_label); - - canvas_draw_str_aligned(canvas, 11, 62, AlignLeft, AlignBottom, "Press=Exit."); - - //Status text and indicator - canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, app->send_status); - - switch(app->send_status_c) { - case 0: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - break; - case 1: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon(canvas, 116, 17, &I_Pin_arrow_up_7x9); - break; - case 2: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon_ex(canvas, 116, 17, &I_Pin_arrow_up_7x9, IconRotation180); - break; - case 3: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation90); - break; - case 4: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation270); - break; - case 5: - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon(canvas, 116, 18, &I_Pin_star_7x7); - break; - } - - //Repeat indicator - //canvas_draw_str_aligned(canvas, 125, 40, AlignRight, AlignBottom, "Repeat:"); - //canvas_draw_icon(canvas, 115, 39, &I_SubGHzRemote_Repeat_12x14); - //canvas_draw_str_aligned(canvas, 125, 62, AlignRight, AlignBottom, int_to_char(app->repeat)); - } - - furi_mutex_release(app->model_mutex); -} - -static void input_callback(InputEvent* input_event, void* ctx) { - SubGHzRemote* app = ctx; - furi_message_queue_put(app->input_queue, input_event, 0); -} - -void subghz_remote_subghz_alloc(SubGHzRemote* app) { - // load subghz presets - app->setting = subghz_setting_alloc(); - subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user")); - - // load mfcodes - app->environment = subghz_environment_alloc(); - subghz_environment_load_keystore(app->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); - subghz_environment_load_keystore( - app->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); - subghz_environment_set_came_atomo_rainbow_table_file_name( - app->environment, EXT_PATH("subghz/assets/came_atomo")); - subghz_environment_set_nice_flor_s_rainbow_table_file_name( - app->environment, EXT_PATH("subghz/assets/nice_flor_s")); - subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - app->environment, EXT_PATH("subghz/assets/alutech_at_4n")); - subghz_environment_set_protocol_registry(app->environment, (void*)&subghz_protocol_registry); - - app->subghz_receiver = subghz_receiver_alloc_init(app->environment); -} - -SubGHzRemote* subghz_remote_alloc(void) { - SubGHzRemote* app = malloc(sizeof(SubGHzRemote)); +SubGhzRemoteApp* subghz_remote_app_alloc() { + SubGhzRemoteApp* app = malloc(sizeof(SubGhzRemoteApp)); // Enable power for External CC1101 if it is connected furi_hal_subghz_enable_ext_power(); @@ -773,24 +34,77 @@ SubGHzRemote* subghz_remote_alloc(void) { furi_hal_power_suppress_charge_enter(); - app->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + app->file_path = furi_string_alloc(); + furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX); - app->input_queue = furi_message_queue_alloc(32, sizeof(InputEvent)); - - app->view_port = view_port_alloc(); - view_port_draw_callback_set(app->view_port, render_callback, app); - view_port_input_callback_set(app->view_port, input_callback, app); - - // Open GUI and register view_port + // GUI app->gui = furi_record_open(RECORD_GUI); - gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); - app->notification = furi_record_open(RECORD_NOTIFICATION); + // View Dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&subrem_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, subghz_remote_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, subghz_remote_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, subghz_remote_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // SubMenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubRemViewSubmenu, submenu_get_view(app->submenu)); + + //Dialog + app->dialogs = furi_record_open(RECORD_DIALOGS); + + // Remote view + app->subrem_remote_view = subrem_view_remote_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SubRemViewIDRemote, + subrem_view_remote_get_view(app->subrem_remote_view)); + + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + app->subs_preset[i] = subrem_sub_file_preset_alloc(); + } + + app->setting = subghz_setting_alloc(); + subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user")); + + app->environment = subghz_environment_alloc(); + + subghz_environment_load_keystore(app->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); + subghz_environment_load_keystore( + app->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + subghz_environment_set_came_atomo_rainbow_table_file_name( + app->environment, EXT_PATH("subghz/assets/came_atomo")); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + app->environment, EXT_PATH("subghz/assets/alutech_at_4n")); + subghz_environment_set_nice_flor_s_rainbow_table_file_name( + app->environment, EXT_PATH("subghz/assets/nice_flor_s")); + subghz_environment_set_protocol_registry(app->environment, (void*)&subghz_protocol_registry); + + app->receiver = subghz_receiver_alloc_init(app->environment); + + app->tx_running = false; + + scene_manager_next_scene(app->scene_manager, SubRemSceneStart); return app; } -void subghz_remote_free(SubGHzRemote* app, bool with_subghz) { +void subghz_remote_app_free(SubGhzRemoteApp* app) { + furi_assert(app); + furi_hal_power_suppress_charge_exit(); // Disable power for External CC1101 if it was enabled and module is connected @@ -798,304 +112,47 @@ void subghz_remote_free(SubGHzRemote* app, bool with_subghz) { // Reinit SPI handles for internal radio / nfc furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - furi_string_free(app->up_file); - furi_string_free(app->down_file); - furi_string_free(app->left_file); - furi_string_free(app->right_file); - furi_string_free(app->ok_file); + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewSubmenu); + submenu_free(app->submenu); - furi_string_free(app->file_path); - furi_string_free(app->signal); + //Dialog + furi_record_close(RECORD_DIALOGS); - gui_remove_view_port(app->gui, app->view_port); - furi_record_close(RECORD_GUI); - view_port_free(app->view_port); - app->gui = NULL; + // Remote view + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDRemote); + subrem_view_remote_free(app->subrem_remote_view); - furi_message_queue_free(app->input_queue); + subghz_receiver_free(app->receiver); + subghz_environment_free(app->environment); + subghz_setting_free(app->setting); - furi_mutex_free(app->model_mutex); - - if(with_subghz) { - furi_hal_subghz_sleep(); - subghz_setting_free(app->setting); - subghz_receiver_free(app->subghz_receiver); - subghz_environment_free(app->environment); + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + subrem_sub_file_preset_free(app->subs_preset[i]); } + // Notifications furi_record_close(RECORD_NOTIFICATION); - app->notification = NULL; + app->notifications = NULL; + + // Close records + furi_record_close(RECORD_GUI); + + // Path strings + furi_string_free(app->file_path); free(app); } int32_t subghz_remote_app(void* p) { UNUSED(p); - SubGHzRemote* app = subghz_remote_alloc(); + SubGhzRemoteApp* subghz_remote_app = subghz_remote_app_alloc(); - app->file_path = furi_string_alloc(); - app->signal = furi_string_alloc(); + furi_string_set(subghz_remote_app->file_path, SUBREM_APP_FOLDER); - //setup variables before population - app->up_file = furi_string_alloc(); - app->down_file = furi_string_alloc(); - app->left_file = furi_string_alloc(); - app->right_file = furi_string_alloc(); - app->ok_file = furi_string_alloc(); + view_dispatcher_run(subghz_remote_app->view_dispatcher); - app->file_result = 3; - - Storage* storage = furi_record_open(RECORD_STORAGE); - storage_common_migrate(storage, EXT_PATH("unirf"), SUBREMOTEMAP_FOLDER); - - if(!storage_simply_mkdir(storage, SUBREMOTEMAP_FOLDER)) { - FURI_LOG_E(TAG, "Could not create folder %s", SUBREMOTEMAP_FOLDER); - } - furi_record_close(RECORD_STORAGE); - - furi_string_set(app->file_path, SUBREMOTEMAP_FOLDER); - - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, SUBREMOTEMAP_EXTENSION, &I_sub1_10px); - browser_options.base_path = SUBREMOTEMAP_FOLDER; - - bool res = dialog_file_browser_show(dialogs, app->file_path, app->file_path, &browser_options); - - furi_record_close(RECORD_DIALOGS); - if(!res) { - FURI_LOG_E(TAG, "No file selected"); - subghz_remote_free(app, false); - return 255; - } else { - //check map and population variables - subghz_remote_cfg_set_check(app, app->file_path); - } - - // init subghz stuff - subghz_remote_subghz_alloc(app); - - bool exit_loop = false; - - if(app->file_result == 2) { - //FURI_LOG_D( - //TAG, - //"U: %s - D: %s - L: %s - R: %s - O: %s ", - //furi_string_get_cstr(app->up_file), - //furi_string_get_cstr(app->down_file), - //furi_string_get_cstr(app->left_file), - //furi_string_get_cstr(app->right_file), - //furi_string_get_cstr(app->ok_file)); - - //variables to control multiple button presses and status updates - app->send_status = "Idle"; - app->send_status_c = 0; - app->processing = 0; - //app->repeat = 1; - app->button = 0; - - //refresh screen to update variables before processing main screen or error screens - furi_mutex_release(app->model_mutex); - view_port_update(app->view_port); - - //input detect loop start - InputEvent input; - while(1) { - furi_check( - furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); - //FURI_LOG_D( - //TAG, - //"key: %s type: %s", - //input_get_key_name(input.key), - //input_get_type_name(input.type)); - - switch(input.key) { - case InputKeyUp: - if(input.type == InputTypePress) { - if(app->up_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->up_file); - app->button = 1; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->up_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyDown: - if(input.type == InputTypePress) { - if(app->down_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->down_file); - app->button = 2; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->down_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyRight: - if(input.type == InputTypePress) { - if(app->right_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->right_file); - app->button = 3; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->right_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyLeft: - if(input.type == InputTypePress) { - if(app->left_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->left_file); - app->button = 4; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->left_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyOk: - if(input.type == InputTypePress) { - if(app->ok_enabled) { - if(app->processing == 0) { - furi_string_reset(app->signal); - furi_string_set(app->signal, app->ok_file); - app->button = 5; - app->processing = 1; - } - } - } - if(input.type == InputTypeRelease) { - if(app->ok_enabled) { - subghz_remote_tx_stop(app); - } - } - break; - - case InputKeyBack: - subghz_remote_tx_stop(app); - exit_loop = true; - break; - default: - break; - } - - if(app->processing == 0) { - //FURI_LOG_D(TAG, "processing 0"); - app->send_status = "Idle"; - app->send_status_c = 0; - app->button = 0; - } else if(app->processing == 1) { - //FURI_LOG_D(TAG, "processing 1"); - - app->send_status = "Send"; - - switch(app->button) { - case 1: - app->send_status_c = 1; - break; - case 2: - app->send_status_c = 2; - break; - case 3: - app->send_status_c = 3; - break; - case 4: - app->send_status_c = 4; - break; - case 5: - app->send_status_c = 5; - break; - } - - app->processing = 2; - - subghz_remote_process_signal(app, app->signal); - } - - if(exit_loop == true) { - furi_mutex_release(app->model_mutex); - break; - } - - furi_mutex_release(app->model_mutex); - view_port_update(app->view_port); - } - } else if(app->file_result == 1 || app->file_result == 3) { - //refresh screen to update variables before processing main screen or error screens - view_port_update(app->view_port); - - InputEvent input; - while(1) { - furi_check( - furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); - //FURI_LOG_D( - //TAG, - //"key: %s type: %s", - //input_get_key_name(input.key), - //input_get_type_name(input.type)); - - switch(input.key) { - case InputKeyRight: - break; - case InputKeyLeft: - break; - case InputKeyUp: - break; - case InputKeyDown: - break; - case InputKeyOk: - break; - case InputKeyBack: - exit_loop = true; - break; - default: - break; - } - - if(exit_loop == true) { - furi_mutex_release(app->model_mutex); - break; - } - - furi_mutex_release(app->model_mutex); - view_port_update(app->view_port); - } - } else { - furi_mutex_release(app->model_mutex); - } - - // remove & free all stuff created by app - subghz_remote_free(app, true); + subghz_remote_app_free(subghz_remote_app); return 0; } diff --git a/applications/main/subghz_remote/subghz_remote_app_i.c b/applications/main/subghz_remote/subghz_remote_app_i.c new file mode 100644 index 000000000..cde4292a4 --- /dev/null +++ b/applications/main/subghz_remote/subghz_remote_app_i.c @@ -0,0 +1,449 @@ +#include "subghz_remote_app_i.h" +#include +#include + +#include + +// #include +// #include + +#include + +#define TAG "SubGhzRemote" + +static const char* map_file_labels[SubRemSubKeyNameMaxCount][2] = { + [SubRemSubKeyNameUp] = {"UP", "ULABEL"}, + [SubRemSubKeyNameDown] = {"DOWN", "DLABEL"}, + [SubRemSubKeyNameLeft] = {"LEFT", "LLABEL"}, + [SubRemSubKeyNameRight] = {"RIGHT", "RLABEL"}, + [SubRemSubKeyNameOk] = {"OK", "OKLABEL"}, +}; + +static bool + subrem_set_preset_data(SubGhzSetting* setting, FreqPreset* freq_preset, const char* preset) { + const char* preset_name = ""; + if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { + preset_name = "AM270"; + } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { + preset_name = "AM650"; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { + preset_name = "FM238"; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { + preset_name = "FM476"; + } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { + // preset_name = "CUSTOM"; + return false; + } else { + FURI_LOG_E(TAG, "Unknown preset"); + return false; + } + size_t preset_index = subghz_setting_get_inx_preset_by_name(setting, preset_name); + freq_preset->data = subghz_setting_get_preset_data(setting, preset_index); + return true; +} + +SubRemSubFilePreset* subrem_sub_file_preset_alloc() { + SubRemSubFilePreset* sub_preset = malloc(sizeof(SubRemSubFilePreset)); + + sub_preset->fff_data = flipper_format_string_alloc(); + sub_preset->file_path = furi_string_alloc(); + sub_preset->protocaol_name = furi_string_alloc(); + sub_preset->label = furi_string_alloc_set_str("N/A"); + + sub_preset->type = SubGhzProtocolTypeUnknown; + + return sub_preset; +} + +void subrem_sub_file_preset_free(SubRemSubFilePreset* sub_preset) { + furi_assert(sub_preset); + + furi_string_free(sub_preset->label); + furi_string_free(sub_preset->protocaol_name); + furi_string_free(sub_preset->file_path); + flipper_format_free(sub_preset->fff_data); + + free(sub_preset); +} + +static void subrem_sub_file_preset_reset(SubRemSubFilePreset* sub_preset) { + furi_assert(sub_preset); + + furi_string_set_str(sub_preset->label, "N/A"); + furi_string_reset(sub_preset->protocaol_name); + furi_string_reset(sub_preset->file_path); + + Stream* fff_data_stream = flipper_format_get_raw_stream(sub_preset->fff_data); + stream_clean(fff_data_stream); + + sub_preset->type = SubGhzProtocolTypeUnknown; +} + +void subrem_map_preset_reset(SubGhzRemoteApp* app) { + furi_assert(app); + + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + subrem_sub_file_preset_reset(app->subs_preset[i]); + } +} + +static bool subrem_map_preset_load(SubGhzRemoteApp* app, FlipperFormat* fff_data_file) { + furi_assert(app); + bool ret = false; + SubRemSubFilePreset* sub_preset; + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = app->subs_preset[i]; + if(!flipper_format_read_string( + fff_data_file, map_file_labels[i][0], sub_preset->file_path)) { +#if FURI_DEBUG + FURI_LOG_W(TAG, "No file patch for %s", map_file_labels[i][0]); +#endif + sub_preset->type = SubGhzProtocolTypeUnknown; + } else if(!flipper_format_rewind(fff_data_file)) { + // Rewind error + } else if(!flipper_format_read_string( + fff_data_file, map_file_labels[i][1], sub_preset->label)) { +#if FURI_DEBUG + FURI_LOG_W(TAG, "No Label for %s", map_file_labels[i][0]); +#endif + // TODO: Standart name or part of name + furi_string_set_str(sub_preset->label, "N/A"); + } else { + FURI_LOG_I( + TAG, + "%-5s: %s %s", + map_file_labels[i][0], + furi_string_get_cstr(sub_preset->label), + furi_string_get_cstr(sub_preset->file_path)); + ret = true; + } + flipper_format_rewind(fff_data_file); + } + return ret; +} + +bool subrem_save_protocol_to_file(FlipperFormat* flipper_format, const char* dev_file_name) { + furi_assert(flipper_format); + furi_assert(dev_file_name); + + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format); + + bool saved = false; + FuriString* file_dir = furi_string_alloc(); + + path_extract_dirname(dev_file_name, file_dir); + do { + //removing additional fields + flipper_format_delete_key(flipper_format, "Repeat"); + //flipper_format_delete_key(flipper_format, "Manufacture"); + + if(!storage_simply_remove(storage, dev_file_name)) { + break; + } + //ToDo check Write + stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); + stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS); + + saved = true; + } while(0); + furi_string_free(file_dir); + furi_record_close(RECORD_STORAGE); + return saved; +} + +bool subrem_tx_start_sub( + SubGhzRemoteApp* app, + SubRemSubFilePreset* sub_preset, + SubGhzProtocolEncoderRAWCallbackEnd callback) { + furi_assert(app); + furi_assert(sub_preset); + bool ret = false; + + subrem_tx_stop_sub(app, true); + + if(sub_preset->type == SubGhzProtocolTypeUnknown) { + return false; + } + + FURI_LOG_I(TAG, "Send %s", furi_string_get_cstr(sub_preset->label)); + + subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK); + keeloq_reset_original_btn(); + subghz_custom_btns_reset(); + + do { + flipper_format_rewind(sub_preset->fff_data); // + + app->transmitter = subghz_transmitter_alloc_init( + app->environment, furi_string_get_cstr(sub_preset->protocaol_name)); + + if(app->transmitter) { + if(subghz_transmitter_deserialize(app->transmitter, sub_preset->fff_data) != + SubGhzProtocolStatusOk) { + FURI_LOG_E(TAG, "Deserialize error!"); + break; + } + furi_hal_subghz_reset(); + furi_hal_subghz_idle(); + furi_hal_subghz_load_custom_preset(sub_preset->freq_preset.data); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + + furi_hal_subghz_idle(); + + furi_hal_subghz_set_frequency_and_path(sub_preset->freq_preset.frequency); + furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + + if(!furi_hal_subghz_tx()) { + FURI_LOG_E(TAG, "Sending not allowed"); + break; + } + + if(sub_preset->type == SubGhzProtocolTypeRAW) { + subghz_protocol_raw_file_encoder_worker_set_callback_end( + (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance( + app->transmitter), + callback, + app); + } + + furi_hal_subghz_start_async_tx(subghz_transmitter_yield, app->transmitter); + + ret = true; + } + } while(false); + + app->tx_running = ret; + + return ret; +} + +static void subghz_tx_stop(SubGhzRemoteApp* app) { + furi_assert(app); + + //Stop TX + furi_hal_subghz_stop_async_tx(); + + subghz_transmitter_stop(app->transmitter); + subghz_transmitter_free(app->transmitter); + furi_hal_subghz_idle(); +} + +bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced) { + furi_assert(app); + SubRemSubFilePreset* sub_preset = app->subs_preset[app->chusen_sub]; + + if(forced || (sub_preset->type != SubGhzProtocolTypeRAW)) { + // SubRemSubKeyTypeRawKey)) { + if(app->tx_running) { + subghz_tx_stop(app); + + if(sub_preset->type == SubGhzProtocolTypeDynamic) { + subrem_save_protocol_to_file( + sub_preset->fff_data, furi_string_get_cstr(sub_preset->file_path)); + + keeloq_reset_mfname(); + keeloq_reset_kl_type(); + keeloq_reset_original_btn(); + subghz_custom_btns_reset(); + star_line_reset_mfname(); + star_line_reset_kl_type(); + } + + app->tx_running = false; + return true; + } + } + + return false; +} + +static bool subrem_map_preset_check(SubGhzRemoteApp* app, FlipperFormat* fff_data_file) { + furi_assert(app); + FuriString* temp_str = furi_string_alloc(); + uint32_t temp_data32; + bool ret = false; + bool sub_preset_loaded = false; + SubRemSubFilePreset* sub_preset; + + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = app->subs_preset[i]; + sub_preset_loaded = false; + if(furi_string_empty(sub_preset->file_path)) { + // FURI_LOG_I(TAG, "Empty file path"); + continue; + } + do { + if(!flipper_format_file_open_existing( + fff_data_file, furi_string_get_cstr(sub_preset->file_path))) { + FURI_LOG_W(TAG, "Error open file %s", furi_string_get_cstr(sub_preset->file_path)); + break; + } + + if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + + if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) || + (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) && + temp_data32 == SUBGHZ_KEY_FILE_VERSION) { + } else { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + //Load frequency + if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) { + FURI_LOG_W(TAG, "Cannot read frequency. Set default frequency"); + sub_preset->freq_preset.frequency = + subghz_setting_get_default_frequency(app->setting); + } else if(!furi_hal_subghz_is_tx_allowed(temp_data32)) { + FURI_LOG_E(TAG, "This frequency can only be used for RX"); + break; + } else { + sub_preset->freq_preset.frequency = temp_data32; + } + + //Load preset + if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { + FURI_LOG_E(TAG, "Missing Preset"); + break; + } + + if(!subrem_set_preset_data( + app->setting, &sub_preset->freq_preset, furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Cannot load preset."); + break; + } + + //Load protocol + if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + + FlipperFormat* fff_data = sub_preset->fff_data; + if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { + //if RAW + subghz_protocol_raw_gen_fff_data( + fff_data, furi_string_get_cstr(sub_preset->file_path)); + } else { + stream_copy_full( + flipper_format_get_raw_stream(fff_data_file), + flipper_format_get_raw_stream(fff_data)); + } + + const SubGhzProtocolRegistry* protocol_registry_items = + subghz_environment_get_protocol_registry(app->environment); + + const SubGhzProtocol* protocol = subghz_protocol_registry_get_by_name( + protocol_registry_items, furi_string_get_cstr(temp_str)); + + if(!protocol) { + FURI_LOG_E(TAG, "Protocol not found"); + break; + } else if(protocol->flag & SubGhzProtocolFlag_Send) { + if((protocol->type == SubGhzProtocolTypeStatic) || + (protocol->type == SubGhzProtocolTypeDynamic) || + // (protocol->type == SubGhzProtocolTypeBinRAW) || // TODO: BINRAW + (protocol->type == SubGhzProtocolTypeRAW)) { + sub_preset->type = protocol->type; + } else { + FURI_LOG_E(TAG, "Unsuported Protocol"); + break; + } + + furi_string_set(sub_preset->protocaol_name, temp_str); + } else { + FURI_LOG_E(TAG, "Protocol does not support transmission"); + } + + sub_preset_loaded = true; + ret |= true; +#if FURI_DEBUG + FURI_LOG_I(TAG, "%-16s - protocol Loaded", furi_string_get_cstr(sub_preset->label)); +#endif + } while(false); + + // TODO: + // Load file state logic + // Label depending on the state + + if(!sub_preset_loaded) { + furi_string_set_str(sub_preset->label, "N/A"); + } + + flipper_format_file_close(fff_data_file); + } + furi_string_free(temp_str); + + return ret; +} + +bool subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path) { + furi_assert(app); + furi_assert(file_path); +#if FURI_DEBUG + FURI_LOG_I(TAG, "Load Map File Start"); +#endif + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + bool ret = false; +#if FURI_DEBUG + FURI_LOG_I(TAG, "Open Map File.."); +#endif + subrem_map_preset_reset(app); + + if(!flipper_format_file_open_existing(fff_data_file, file_path)) { + FURI_LOG_E(TAG, "Could not open MAP file %s", file_path); + } else { + if(!subrem_map_preset_load(app, fff_data_file)) { + FURI_LOG_E(TAG, "Could no Sub file path in MAP file"); + // ret = // error for popup + } else if( + (flipper_format_file_close(fff_data_file)) && + (subrem_map_preset_check(app, fff_data_file))) { + FURI_LOG_I(TAG, "Load Map File Seccesful"); + ret = true; + } + } + + // TODO: Popup for error or return error type + if(!ret) { + FURI_LOG_E(TAG, "Broken Map File"); + } + + flipper_format_file_close(fff_data_file); + flipper_format_free(fff_data_file); + + furi_record_close(RECORD_STORAGE); + + return ret; +} + +SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) { + furi_assert(app); + + FuriString* file_path = furi_string_alloc(); + SubRemLoadMapState ret = SubRemLoadMapStateBack; + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, SUBREM_APP_EXTENSION, &I_sub1_10px); + browser_options.base_path = SUBREM_APP_FOLDER; + + // Input events and views are managed by file_select + if(!dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) { + } else if(subrem_map_file_load(app, furi_string_get_cstr(app->file_path))) { + ret = SubRemLoadMapStateOK; + } else { + ret = SubRemLoadMapStateError; + } + + furi_string_free(file_path); + + return ret; +} diff --git a/applications/main/subghz_remote/subghz_remote_app_i.h b/applications/main/subghz_remote/subghz_remote_app_i.h new file mode 100644 index 000000000..98cb4ac48 --- /dev/null +++ b/applications/main/subghz_remote/subghz_remote_app_i.h @@ -0,0 +1,84 @@ +#pragma once + +#include "helpers/subrem_types.h" +#include + +#include "views/remote.h" + +#include "scenes/subrem_scene.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#define SUBREM_APP_FOLDER ANY_PATH("subghz_remote") +#define SUBREM_MAX_LEN_NAME 64 + +typedef struct { + uint32_t frequency; + uint8_t* data; +} FreqPreset; + +// Sub File preset +typedef struct { + FlipperFormat* fff_data; + FreqPreset freq_preset; + FuriString* file_path; + FuriString* protocaol_name; + FuriString* label; + SubGhzProtocolType type; +} SubRemSubFilePreset; + +SubRemSubFilePreset* subrem_sub_file_preset_alloc(); + +void subrem_sub_file_preset_free(SubRemSubFilePreset* sub_preset); + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Submenu* submenu; + FuriString* file_path; + char file_name_tmp[SUBREM_MAX_LEN_NAME]; + + SubRemViewRemote* subrem_remote_view; + + SubRemSubFilePreset* subs_preset[SubRemSubKeyNameMaxCount]; + + SubGhzSetting* setting; + SubGhzEnvironment* environment; + SubGhzReceiver* receiver; + SubGhzTransmitter* transmitter; + + bool tx_running; + + uint8_t chusen_sub; + + // TODO: LoadFileError +} SubGhzRemoteApp; + +SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app); + +bool subrem_tx_start_sub( + SubGhzRemoteApp* app, + SubRemSubFilePreset* sub_preset, + SubGhzProtocolEncoderRAWCallbackEnd callback); + +bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced); \ No newline at end of file diff --git a/applications/main/subghz_remote/views/remote.c b/applications/main/subghz_remote/views/remote.c new file mode 100644 index 000000000..85c63568e --- /dev/null +++ b/applications/main/subghz_remote/views/remote.c @@ -0,0 +1,302 @@ +#include "remote.h" +#include "../subghz_remote_app_i.h" + +#include +#include + +#define SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH 16 + +struct SubRemViewRemote { + View* view; + SubRemViewRemoteCallback callback; + void* context; +}; + +// TODO: model +typedef struct { + // FuriString* up_label; + // FuriString* down_label; + // FuriString* left_label; + // FuriString* right_label; + // FuriString* ok_label; + + char* up_label; + char* down_label; + char* left_label; + char* right_label; + char* ok_label; + + SubRemViewRemoteState state; + + uint8_t pressed_btn; +} SubRemViewRemoteModel; + +void subrem_view_remote_set_callback( + SubRemViewRemote* subrem_view_remote, + SubRemViewRemoteCallback callback, + void* context) { + furi_assert(subrem_view_remote); + + subrem_view_remote->callback = callback; + subrem_view_remote->context = context; +} + +void subrem_view_remote_add_data_to_show( + SubRemViewRemote* subrem_view_remote, + const char* up_label, + const char* down_label, + const char* left_label, + const char* right_label, + const char* ok_label) { + furi_assert(subrem_view_remote); + + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { + strncpy(model->up_label, up_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); + strncpy(model->down_label, down_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); + strncpy(model->left_label, left_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); + strncpy(model->right_label, right_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); + strncpy(model->ok_label, ok_label, SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); + + // furi_string_set(model->up_label, up_label); + // furi_string_set(model->down_label, down_label); + // furi_string_set(model->left_label, left_label); + // furi_string_set(model->right_label, right_label); + // furi_string_set(model->ok_label, ok_label); + }, + true); +} + +void subrem_view_remote_set_presed_btn(SubRemViewRemote* subrem_view_remote, uint8_t presed_btn) { + furi_assert(subrem_view_remote); + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { model->pressed_btn = presed_btn; }, + true); +} + +void subrem_view_remote_set_state( + SubRemViewRemote* subrem_view_remote, + SubRemViewRemoteState state) { + furi_assert(subrem_view_remote); + with_view_model( + subrem_view_remote->view, SubRemViewRemoteModel * model, { model->state = state; }, true); +} + +void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_clear(canvas); + + //Icons for Labels + //canvas_draw_icon(canvas, 0, 0, &I_SubGHzRemote_LeftAlignedButtons_9x64); + canvas_draw_icon(canvas, 1, 5, &I_ButtonUp_7x4); + canvas_draw_icon(canvas, 1, 15, &I_ButtonDown_7x4); + canvas_draw_icon(canvas, 2, 23, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 2, 33, &I_ButtonRight_4x7); + canvas_draw_icon(canvas, 0, 42, &I_Ok_btn_9x9); + canvas_draw_icon(canvas, 0, 53, &I_back_10px); + + //Labels + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 10, 10, model->up_label); + canvas_draw_str(canvas, 10, 20, model->down_label); + canvas_draw_str(canvas, 10, 30, model->left_label); + canvas_draw_str(canvas, 10, 40, model->right_label); + canvas_draw_str(canvas, 10, 50, model->ok_label); + + // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); + // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); + // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); + // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); + // canvas_draw_str(canvas, 10, 10, furi_string_get_cstr(model->up_label)); + + canvas_draw_str_aligned(canvas, 11, 62, AlignLeft, AlignBottom, "Hold=Exit."); + + //Status text and indicator + canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); + + if(model->state == SubRemViewRemoteStateIdle) { + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Idle"); + } else { + switch(model->state) { + case SubRemViewRemoteStateSending: + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Send"); + break; + case SubRemViewRemoteStateLoading: + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Load"); + break; + default: +#if FURI_DEBUG + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Wrong_state"); +#endif + break; + } + + switch(model->pressed_btn) { + case SubRemSubKeyNameUp: + canvas_draw_icon(canvas, 116, 17, &I_Pin_arrow_up_7x9); + break; + case SubRemSubKeyNameDown: + canvas_draw_icon_ex(canvas, 116, 17, &I_Pin_arrow_up_7x9, IconRotation180); + break; + case SubRemSubKeyNameLeft: + canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation270); + break; + case SubRemSubKeyNameRight: + canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation90); + break; + case SubRemSubKeyNameOk: + canvas_draw_icon(canvas, 116, 18, &I_Pin_star_7x7); + break; + } + } + //Repeat indicator + //canvas_draw_str_aligned(canvas, 125, 40, AlignRight, AlignBottom, "Repeat:"); + //canvas_draw_icon(canvas, 115, 39, &I_SubGHzRemote_Repeat_12x14); + //canvas_draw_str_aligned(canvas, 125, 62, AlignRight, AlignBottom, int_to_char(app->repeat)); +} + +bool subrem_view_remote_input(InputEvent* event, void* context) { + furi_assert(context); + SubRemViewRemote* subrem_view_remote = context; + + if(event->key == InputKeyBack && event->type == InputTypeLong) { + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { + strcpy(model->up_label, "N/A"); + strcpy(model->down_label, "N/A"); + strcpy(model->left_label, "N/A"); + strcpy(model->right_label, "N/A"); + strcpy(model->ok_label, "N/A"); + }, + false); + subrem_view_remote->callback(SubRemCustomEventViewRemoteBack, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyBack && event->type == InputTypeShort) { + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { model->pressed_btn = 0; }, + true); + subrem_view_remote->callback( + SubRemCustomEventViewRemoteForcedStop, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyBack) { + return true; + } + // BACK button processing end + + if(event->key == InputKeyUp && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartUP, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyDown && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartDOWN, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyLeft && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartLEFT, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyRight && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartRIGHT, subrem_view_remote->context); + return true; + } else if(event->key == InputKeyOk && event->type == InputTypePress) { + subrem_view_remote->callback( + SubRemCustomEventViewRemoteStartOK, subrem_view_remote->context); + return true; + } else if(event->type == InputTypeRelease) { + subrem_view_remote->callback(SubRemCustomEventViewRemoteStop, subrem_view_remote->context); + return true; + } + + return true; +} + +void subrem_view_remote_enter(void* context) { + furi_assert(context); +} + +void subrem_view_remote_exit(void* context) { + furi_assert(context); +} + +SubRemViewRemote* subrem_view_remote_alloc() { + SubRemViewRemote* subrem_view_remote = malloc(sizeof(SubRemViewRemote)); + + // View allocation and configuration + subrem_view_remote->view = view_alloc(); + view_allocate_model( + subrem_view_remote->view, ViewModelTypeLocking, sizeof(SubRemViewRemoteModel)); + view_set_context(subrem_view_remote->view, subrem_view_remote); + view_set_draw_callback(subrem_view_remote->view, (ViewDrawCallback)subrem_view_remote_draw); + view_set_input_callback(subrem_view_remote->view, subrem_view_remote_input); + view_set_enter_callback(subrem_view_remote->view, subrem_view_remote_enter); + view_set_exit_callback(subrem_view_remote->view, subrem_view_remote_exit); + + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { + model->state = SubRemViewRemoteStateIdle; + + model->up_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); + model->down_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); + model->left_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); + model->right_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); + model->ok_label = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); + + strcpy(model->up_label, "N/A"); + strcpy(model->down_label, "N/A"); + strcpy(model->left_label, "N/A"); + strcpy(model->right_label, "N/A"); + strcpy(model->ok_label, "N/A"); + + // model->up_label = furi_string_alloc_set_str("N/A"); + // model->down_label = furi_string_alloc_set_str("N/A"); + // model->left_label = furi_string_alloc_set_str("N/A"); + // model->right_label = furi_string_alloc_set_str("N/A"); + // model->ok_label = furi_string_alloc_set_str("N/A"); + + model->pressed_btn = 0; + }, + true); + return subrem_view_remote; +} + +void subrem_view_remote_free(SubRemViewRemote* subghz_remote) { + furi_assert(subghz_remote); + + with_view_model( + subghz_remote->view, + SubRemViewRemoteModel * model, + { + free(model->up_label); + free(model->down_label); + free(model->left_label); + free(model->right_label); + free(model->ok_label); + + // furi_string_free(model->up_label); + // furi_string_free(model->down_label); + // furi_string_free(model->left_label); + // furi_string_free(model->right_label); + // furi_string_free(model->ok_label); + }, + true); + view_free(subghz_remote->view); + free(subghz_remote); +} + +View* subrem_view_remote_get_view(SubRemViewRemote* subrem_view_remote) { + furi_assert(subrem_view_remote); + return subrem_view_remote->view; +} \ No newline at end of file diff --git a/applications/main/subghz_remote/views/remote.h b/applications/main/subghz_remote/views/remote.h new file mode 100644 index 000000000..76121cf8a --- /dev/null +++ b/applications/main/subghz_remote/views/remote.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include "../helpers/subrem_custom_event.h" + +typedef enum { + SubRemViewRemoteStateIdle, + SubRemViewRemoteStateLoading, + SubRemViewRemoteStateSending, +} SubRemViewRemoteState; + +typedef struct SubRemViewRemote SubRemViewRemote; + +typedef void (*SubRemViewRemoteCallback)(SubRemCustomEvent event, void* context); + +void subrem_view_remote_set_callback( + SubRemViewRemote* subrem_view_remote, + SubRemViewRemoteCallback callback, + void* context); + +SubRemViewRemote* subrem_view_remote_alloc(); + +void subrem_view_remote_free(SubRemViewRemote* subrem_view_remote); + +View* subrem_view_remote_get_view(SubRemViewRemote* subrem_view_remote); + +void subrem_view_remote_add_data_to_show( + SubRemViewRemote* subrem_view_remote, + const char* up_label, + const char* down_label, + const char* left_label, + const char* right_label, + const char* ok_label); + +void subrem_view_remote_set_presed_btn(SubRemViewRemote* subrem_view_remote, uint8_t presed_btn); +void subrem_view_remote_set_state( + SubRemViewRemote* subrem_view_remote, + SubRemViewRemoteState state); \ No newline at end of file From 6f9f3e949617d997492ba46c1c6f60a1cefaff30 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Thu, 18 May 2023 15:16:31 +0300 Subject: [PATCH 33/40] minor fixes --- applications/main/subghz_remote/scenes/subrem_scene_remote.c | 1 + applications/main/subghz_remote/subghz_remote_app_i.c | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz_remote/scenes/subrem_scene_remote.c b/applications/main/subghz_remote/scenes/subrem_scene_remote.c index d543ca999..c24583233 100644 --- a/applications/main/subghz_remote/scenes/subrem_scene_remote.c +++ b/applications/main/subghz_remote/scenes/subrem_scene_remote.c @@ -93,6 +93,7 @@ bool subrem_scene_remote_on_event(void* context, SceneManagerEvent event) { notification_message(app->notifications, &sequence_blink_start_magenta); } else { subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle); + notification_message(app->notifications, &sequence_blink_stop); } return true; } else if(event.event == SubRemCustomEventViewRemoteForcedStop) { diff --git a/applications/main/subghz_remote/subghz_remote_app_i.c b/applications/main/subghz_remote/subghz_remote_app_i.c index cde4292a4..9b0f77d17 100644 --- a/applications/main/subghz_remote/subghz_remote_app_i.c +++ b/applications/main/subghz_remote/subghz_remote_app_i.c @@ -106,8 +106,7 @@ static bool subrem_map_preset_load(SubGhzRemoteApp* app, FlipperFormat* fff_data #if FURI_DEBUG FURI_LOG_W(TAG, "No Label for %s", map_file_labels[i][0]); #endif - // TODO: Standart name or part of name - furi_string_set_str(sub_preset->label, "N/A"); + path_extract_filename(sub_preset->file_path, sub_preset->label, true); } else { FURI_LOG_I( TAG, From 6f205edd9a6392b1a58155bc4fcab71eb4246076 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Thu, 18 May 2023 19:20:34 +0300 Subject: [PATCH 34/40] minor fixes --- applications/main/subghz_remote/subghz_remote_app.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/applications/main/subghz_remote/subghz_remote_app.c b/applications/main/subghz_remote/subghz_remote_app.c index de80c9cdd..0b3f645ab 100644 --- a/applications/main/subghz_remote/subghz_remote_app.c +++ b/applications/main/subghz_remote/subghz_remote_app.c @@ -35,7 +35,7 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { furi_hal_power_suppress_charge_enter(); app->file_path = furi_string_alloc(); - furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX); + furi_string_set(app->file_path, SUBREM_APP_FOLDER); // GUI app->gui = furi_record_open(RECORD_GUI); @@ -97,7 +97,11 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { app->tx_running = false; +#ifdef SUBREM_LIGHT + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); +#else scene_manager_next_scene(app->scene_manager, SubRemSceneStart); +#endif return app; } From 99dde674cabb5fb7447e48579d680a02d47c667d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 19 May 2023 03:31:46 +0300 Subject: [PATCH 35/40] small changes --- applications/main/subghz/application.fam | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 6cd28bd85..60cf0c0b7 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -31,6 +31,6 @@ App( appid="subghz_load_dangerous_settings", apptype=FlipperAppType.STARTUP, entry_point="subghz_dangerous_freq", - requires=["subghz"], - order=41, + requires=["storage", "subghz"], + order=500, ) From a7ee5fea93ad424c159b9a9a242a13d6b79f5c8f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 19 May 2023 04:33:14 +0300 Subject: [PATCH 36/40] Name changer as service + extra checks --- applications/services/application.fam | 1 + applications/services/desktop/desktop.c | 23 ---------- .../services/namechanger/application.fam | 8 ++++ .../services/namechanger/namechange.c | 46 +++++++++++++++++++ lib/toolbox/namechanger.c | 14 ++++++ 5 files changed, 69 insertions(+), 23 deletions(-) create mode 100644 applications/services/namechanger/application.fam create mode 100644 applications/services/namechanger/namechange.c diff --git a/applications/services/application.fam b/applications/services/application.fam index aec49b231..afa69301d 100644 --- a/applications/services/application.fam +++ b/applications/services/application.fam @@ -9,5 +9,6 @@ App( "desktop", "loader", "power", + "namechange_srv", ], ) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 25e2017c2..77999dfcc 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -9,9 +9,6 @@ #include #include -#include -#include - #include "animations/animation_manager.h" #include "desktop/scenes/desktop_scene.h" #include "desktop/scenes/desktop_scene_i.h" @@ -429,26 +426,6 @@ int32_t desktop_srv(void* p) { scene_manager_next_scene(desktop->scene_manager, DesktopSceneFault); } - // I added some very bydlo kod here, and thrown some delays to make it worse, pls don't look at it, it will make you cry from laugh - if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { - if(NameChanger_Init()) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); - furi_delay_ms(2); - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); - - furi_delay_ms(3); - Bt* bt = furi_record_open(RECORD_BT); - if(!bt_set_profile(bt, BtProfileSerial)) { - FURI_LOG_D(TAG, "Failed to touch bluetooth to name change"); - } - furi_record_close(RECORD_BT); - bt = NULL; - furi_delay_ms(3); - } - } - view_dispatcher_run(desktop->view_dispatcher); desktop_free(desktop); diff --git a/applications/services/namechanger/application.fam b/applications/services/namechanger/application.fam new file mode 100644 index 000000000..a4bc23938 --- /dev/null +++ b/applications/services/namechanger/application.fam @@ -0,0 +1,8 @@ +App( + appid="namechange_srv", + apptype=FlipperAppType.STARTUP, + entry_point="namechange_on_system_start", + requires=["storage", "cli", "bt"], + conflicts=["updater"], + order=600, +) \ No newline at end of file diff --git a/applications/services/namechanger/namechange.c b/applications/services/namechanger/namechange.c new file mode 100644 index 000000000..22cb0d9de --- /dev/null +++ b/applications/services/namechanger/namechange.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +#include +#include + +int32_t namechange_on_system_start(void* p) { + UNUSED(p); + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { + return 0; + } + + // Wait for all required services to start and create their records + uint8_t timeout = 0; + while(!furi_record_exists(RECORD_CLI) || !furi_record_exists(RECORD_BT) || + !furi_record_exists(RECORD_STORAGE)) { + timeout++; + if(timeout > 250) { + return 0; + } + furi_delay_ms(5); + } + + // Hehe bad code now here, bad bad bad, very bad, bad example, dont take it, make it better + + if(NameChanger_Init()) { + Cli* cli = furi_record_open(RECORD_CLI); + cli_session_close(cli); + furi_delay_ms(2); // why i added delays here + cli_session_open(cli, &cli_vcp); + furi_record_close(RECORD_CLI); + + furi_delay_ms(3); + Bt* bt = furi_record_open(RECORD_BT); + if(!bt_set_profile(bt, BtProfileSerial)) { + //FURI_LOG_D(TAG, "Failed to touch bluetooth to name change"); + } + furi_record_close(RECORD_BT); + bt = NULL; + furi_delay_ms(3); + } + + return 0; +} \ No newline at end of file diff --git a/lib/toolbox/namechanger.c b/lib/toolbox/namechanger.c index 33842bcd7..94e08ac13 100644 --- a/lib/toolbox/namechanger.c +++ b/lib/toolbox/namechanger.c @@ -6,6 +6,20 @@ bool NameChanger_Init() { Storage* storage = furi_record_open(RECORD_STORAGE); + + // Kostil + velosiped = top ficha + uint8_t timeout = 0; + while(timeout < 11) { + if(storage_sd_status(storage) == FSE_OK) break; + furi_delay_ms(250); + timeout++; + /*if(timeout == 10) { + // Failed to init namechanger, SD card not ready + furi_record_close(RECORD_STORAGE); + return false; + }*/ + } + FuriString* str = furi_string_alloc(); FlipperFormat* file = flipper_format_file_alloc(storage); From 324186dad2917372f520fd7e55d499b33d126e89 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 19 May 2023 04:49:29 +0300 Subject: [PATCH 37/40] Add back cnt in debug --- .../scenes/subghz_scene_radio_settings.c | 84 ++++++++++++++----- 1 file changed, 65 insertions(+), 19 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 8e8d05019..b60ea0b2d 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -25,11 +25,14 @@ const char* const debug_pin_text[DEBUG_P_COUNT] = { "17(1W)", }; -#define DEBUG_COUNTER_COUNT 3 +#define DEBUG_COUNTER_COUNT 6 const char* const debug_counter_text[DEBUG_COUNTER_COUNT] = { "+1", "+2", "+3", + "+4", + "+5", + "+10", }; static void subghz_scene_ext_module_changed(VariableItem* item) { @@ -71,6 +74,15 @@ static void subghz_scene_receiver_config_set_debug_counter(VariableItem* item) { case 2: furi_hal_subghz_set_rolling_counter_mult(3); break; + case 3: + furi_hal_subghz_set_rolling_counter_mult(4); + break; + case 4: + furi_hal_subghz_set_rolling_counter_mult(5); + break; + case 5: + furi_hal_subghz_set_rolling_counter_mult(10); + break; default: break; } @@ -140,24 +152,58 @@ void subghz_scene_radio_settings_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, timestamp_names_text[value_index]); - item = variable_item_list_add( - subghz->variable_item_list, - "Counter incr.", - DEBUG_COUNTER_COUNT, - subghz_scene_receiver_config_set_debug_counter, - subghz); - switch(furi_hal_subghz_get_rolling_counter_mult()) { - case 1: - value_index = 0; - break; - case 2: - value_index = 1; - break; - case 3: - value_index = 2; - break; - default: - break; + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + item = variable_item_list_add( + subghz->variable_item_list, + "Counter incr.", + DEBUG_COUNTER_COUNT, + subghz_scene_receiver_config_set_debug_counter, + subghz); + switch(furi_hal_subghz_get_rolling_counter_mult()) { + case 1: + value_index = 0; + break; + case 2: + value_index = 1; + break; + case 3: + value_index = 2; + break; + case 4: + value_index = 3; + break; + case 5: + value_index = 4; + break; + case 10: + value_index = 5; + break; + default: + break; + } + } else { + item = variable_item_list_add( + subghz->variable_item_list, + "Counter incr.", + 3, + subghz_scene_receiver_config_set_debug_counter, + subghz); + switch(furi_hal_subghz_get_rolling_counter_mult()) { + case 1: + value_index = 0; + break; + case 2: + value_index = 1; + break; + case 3: + value_index = 2; + break; + default: + // Reset to default value + value_index = 0; + furi_hal_subghz_set_rolling_counter_mult(1); + break; + } } variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, debug_counter_text[value_index]); From 481a8e0f33bb32dfedd8d0650bef1b07189ccd80 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 19 May 2023 04:51:25 +0300 Subject: [PATCH 38/40] Move after nchgr service --- applications/main/subghz/application.fam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 60cf0c0b7..0b403feda 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -32,5 +32,5 @@ App( apptype=FlipperAppType.STARTUP, entry_point="subghz_dangerous_freq", requires=["storage", "subghz"], - order=500, + order=650, ) From 10d926de0df79fd590d044646a4009075a79bbb6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 19 May 2023 05:09:14 +0300 Subject: [PATCH 39/40] EXT_PATH only --- applications/main/subghz_remote/subghz_remote_app_i.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz_remote/subghz_remote_app_i.h b/applications/main/subghz_remote/subghz_remote_app_i.h index 98cb4ac48..1cbdbd5cd 100644 --- a/applications/main/subghz_remote/subghz_remote_app_i.h +++ b/applications/main/subghz_remote/subghz_remote_app_i.h @@ -26,7 +26,7 @@ #include #include -#define SUBREM_APP_FOLDER ANY_PATH("subghz_remote") +#define SUBREM_APP_FOLDER EXT_PATH("subghz_remote") #define SUBREM_MAX_LEN_NAME 64 typedef struct { From e7965c6d95ceb465296f53b3a49f146b719d11e5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 19 May 2023 06:33:15 +0300 Subject: [PATCH 40/40] De-libify name changer --- applications/services/application.fam | 2 +- .../services/namechanger/application.fam | 4 +- .../services/namechanger/namechange.c | 46 ------------------- .../services/namechanger}/namechanger.c | 46 ++++++++++++++++++- .../services/namechanger/namechanger.h | 5 ++ .../desktop_settings/desktop_settings_app.c | 2 +- lib/toolbox/namechanger.h | 18 -------- 7 files changed, 54 insertions(+), 69 deletions(-) delete mode 100644 applications/services/namechanger/namechange.c rename {lib/toolbox => applications/services/namechanger}/namechanger.c (60%) create mode 100644 applications/services/namechanger/namechanger.h delete mode 100644 lib/toolbox/namechanger.h diff --git a/applications/services/application.fam b/applications/services/application.fam index afa69301d..0b5009096 100644 --- a/applications/services/application.fam +++ b/applications/services/application.fam @@ -9,6 +9,6 @@ App( "desktop", "loader", "power", - "namechange_srv", + "namechanger_srv", ], ) diff --git a/applications/services/namechanger/application.fam b/applications/services/namechanger/application.fam index a4bc23938..0eaeab987 100644 --- a/applications/services/namechanger/application.fam +++ b/applications/services/namechanger/application.fam @@ -1,7 +1,7 @@ App( - appid="namechange_srv", + appid="namechanger_srv", apptype=FlipperAppType.STARTUP, - entry_point="namechange_on_system_start", + entry_point="namechanger_on_system_start", requires=["storage", "cli", "bt"], conflicts=["updater"], order=600, diff --git a/applications/services/namechanger/namechange.c b/applications/services/namechanger/namechange.c deleted file mode 100644 index 22cb0d9de..000000000 --- a/applications/services/namechanger/namechange.c +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include -#include -#include - -#include -#include - -int32_t namechange_on_system_start(void* p) { - UNUSED(p); - if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { - return 0; - } - - // Wait for all required services to start and create their records - uint8_t timeout = 0; - while(!furi_record_exists(RECORD_CLI) || !furi_record_exists(RECORD_BT) || - !furi_record_exists(RECORD_STORAGE)) { - timeout++; - if(timeout > 250) { - return 0; - } - furi_delay_ms(5); - } - - // Hehe bad code now here, bad bad bad, very bad, bad example, dont take it, make it better - - if(NameChanger_Init()) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); - furi_delay_ms(2); // why i added delays here - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); - - furi_delay_ms(3); - Bt* bt = furi_record_open(RECORD_BT); - if(!bt_set_profile(bt, BtProfileSerial)) { - //FURI_LOG_D(TAG, "Failed to touch bluetooth to name change"); - } - furi_record_close(RECORD_BT); - bt = NULL; - furi_delay_ms(3); - } - - return 0; -} \ No newline at end of file diff --git a/lib/toolbox/namechanger.c b/applications/services/namechanger/namechanger.c similarity index 60% rename from lib/toolbox/namechanger.c rename to applications/services/namechanger/namechanger.c index 94e08ac13..796dd2d3b 100644 --- a/lib/toolbox/namechanger.c +++ b/applications/services/namechanger/namechanger.c @@ -1,10 +1,15 @@ #include "namechanger.h" +#include #include +#include +#include +#include +#include #include #define TAG "NameChanger" -bool NameChanger_Init() { +static bool namechanger_init() { Storage* storage = furi_record_open(RECORD_STORAGE); // Kostil + velosiped = top ficha @@ -64,4 +69,43 @@ bool NameChanger_Init() { furi_string_free(str); return res; +} + +int32_t namechanger_on_system_start(void* p) { + UNUSED(p); + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { + return 0; + } + + // Wait for all required services to start and create their records + uint8_t timeout = 0; + while(!furi_record_exists(RECORD_CLI) || !furi_record_exists(RECORD_BT) || + !furi_record_exists(RECORD_STORAGE)) { + timeout++; + if(timeout > 250) { + return 0; + } + furi_delay_ms(5); + } + + // Hehe bad code now here, bad bad bad, very bad, bad example, dont take it, make it better + + if(namechanger_init()) { + Cli* cli = furi_record_open(RECORD_CLI); + cli_session_close(cli); + furi_delay_ms(2); // why i added delays here + cli_session_open(cli, &cli_vcp); + furi_record_close(RECORD_CLI); + + furi_delay_ms(3); + Bt* bt = furi_record_open(RECORD_BT); + if(!bt_set_profile(bt, BtProfileSerial)) { + //FURI_LOG_D(TAG, "Failed to touch bluetooth to name change"); + } + furi_record_close(RECORD_BT); + bt = NULL; + furi_delay_ms(3); + } + + return 0; } \ No newline at end of file diff --git a/applications/services/namechanger/namechanger.h b/applications/services/namechanger/namechanger.h new file mode 100644 index 000000000..dac1b7952 --- /dev/null +++ b/applications/services/namechanger/namechanger.h @@ -0,0 +1,5 @@ +#pragma once + +#define NAMECHANGER_HEADER "Flipper Name File" +#define NAMECHANGER_VERSION 1 +#define NAMECHANGER_PATH EXT_PATH("dolphin/name.settings") diff --git a/applications/settings/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c index 317d57654..7e04271f2 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include diff --git a/lib/toolbox/namechanger.h b/lib/toolbox/namechanger.h deleted file mode 100644 index bfaadec15..000000000 --- a/lib/toolbox/namechanger.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#define NAMECHANGER_HEADER "Flipper Name File" -#define NAMECHANGER_VERSION 1 -#define NAMECHANGER_PATH EXT_PATH("dolphin/name.settings") - -#include "stdbool.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// Initializes the name changer. (Load name file, apply changes) -bool NameChanger_Init(); - -#ifdef __cplusplus -} -#endif \ No newline at end of file