diff --git a/applications/debug/unit_tests/nfc/nfc_transport.c b/applications/debug/unit_tests/nfc/nfc_transport.c index e2e313fde..e9f4e2134 100644 --- a/applications/debug/unit_tests/nfc/nfc_transport.c +++ b/applications/debug/unit_tests/nfc/nfc_transport.c @@ -455,4 +455,19 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { return NfcErrorNone; } +NfcError nfc_felica_listener_set_sensf_res_data( + Nfc* instance, + const uint8_t* idm, + const uint8_t idm_len, + const uint8_t* pmm, + const uint8_t pmm_len) { + furi_assert(instance); + furi_assert(idm); + furi_assert(pmm); + furi_assert(idm_len == 8); + furi_assert(pmm_len == 8); + + return NfcErrorNone; +} + #endif diff --git a/applications/external/uhf_rfid/application.fam b/applications/external/uhf_rfid/application.fam new file mode 100644 index 000000000..eafbc83e1 --- /dev/null +++ b/applications/external/uhf_rfid/application.fam @@ -0,0 +1,17 @@ +App( + appid="uhf_rfid", + name="[YRM100] UHF RFID", + apptype=FlipperAppType.EXTERNAL, + targets=["f7"], + entry_point="uhf_app_main", + requires=[ + "storage", + "gui", + ], + stack_size=8 * 1024, + order=30, + fap_icon="uhf_10px.png", + fap_category="GPIO", + fap_icon_assets="icons", + fap_icon_assets_symbol="uhf_rfid", +) diff --git a/applications/external/uhf_rfid/scenes/uhf_scene.c b/applications/external/uhf_rfid/scenes/uhf_scene.c new file mode 100644 index 000000000..23c5cfb0f --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene.c @@ -0,0 +1,30 @@ +#include "uhf_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const uhf_on_enter_handlers[])(void*) = { +#include "uhf_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 uhf_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "uhf_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 uhf_on_exit_handlers[])(void* context) = { +#include "uhf_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers uhf_scene_handlers = { + .on_enter_handlers = uhf_on_enter_handlers, + .on_event_handlers = uhf_on_event_handlers, + .on_exit_handlers = uhf_on_exit_handlers, + .scene_num = UHFSceneNum, +}; diff --git a/applications/external/uhf_rfid/scenes/uhf_scene.h b/applications/external/uhf_rfid/scenes/uhf_scene.h new file mode 100644 index 000000000..f7c39c3f8 --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) UHFScene##id, +typedef enum { +#include "uhf_scene_config.h" + UHFSceneNum, +} UHFScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers uhf_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "uhf_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 "uhf_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 "uhf_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_config.h b/applications/external/uhf_rfid/scenes/uhf_scene_config.h new file mode 100644 index 000000000..2fe83c6c1 --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_config.h @@ -0,0 +1,18 @@ +ADD_SCENE(uhf, verify, Verify) +ADD_SCENE(uhf, start, Start) +ADD_SCENE(uhf, read_tag, ReadTag) +ADD_SCENE(uhf, read_tag_success, ReadTagSuccess) +ADD_SCENE(uhf, tag_menu, TagMenu) +ADD_SCENE(uhf, save_name, SaveName) +ADD_SCENE(uhf, save_success, SaveSuccess) +ADD_SCENE(uhf, saved_menu, SavedMenu) +ADD_SCENE(uhf, file_select, FileSelect) +ADD_SCENE(uhf, device_info, DeviceInfo) +ADD_SCENE(uhf, delete, Delete) +ADD_SCENE(uhf, delete_success, DeleteSuccess) +ADD_SCENE(uhf, write_tag, WriteTag) +ADD_SCENE(uhf, write_tag_success, WriteTagSuccess) +ADD_SCENE(uhf, settings, Settings) +// ADD_SCENE(uhf, read_factory_success, ReadFactorySuccess) +// ADD_SCENE(uhf, write_key, WriteKey) +// ADD_SCENE(uhf, key_menu, KeyMenu) diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_delete.c b/applications/external/uhf_rfid/scenes/uhf_scene_delete.c new file mode 100644 index 000000000..001068180 --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_delete.c @@ -0,0 +1,50 @@ +#include "../uhf_app_i.h" + +void uhf_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) { + UHFApp* uhf_app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result); + } +} + +void uhf_scene_delete_on_enter(void* context) { + UHFApp* uhf_app = context; + + // Setup Custom Widget view + char temp_str[64]; + snprintf(temp_str, sizeof(temp_str), "\e#Delete %s?\e#", uhf_app->uhf_device->dev_name); + widget_add_text_box_element( + uhf_app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, temp_str, false); + widget_add_button_element( + uhf_app->widget, GuiButtonTypeLeft, "Back", uhf_scene_delete_widget_callback, uhf_app); + widget_add_button_element( + uhf_app->widget, GuiButtonTypeRight, "Delete", uhf_scene_delete_widget_callback, uhf_app); + + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget); +} + +bool uhf_scene_delete_on_event(void* context, SceneManagerEvent event) { + UHFApp* uhf_app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + return scene_manager_previous_scene(uhf_app->scene_manager); + } else if(event.event == GuiButtonTypeRight) { + if(uhf_device_delete(uhf_app->uhf_device, true)) { + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneDeleteSuccess); + } else { + scene_manager_search_and_switch_to_previous_scene( + uhf_app->scene_manager, UHFSceneStart); + } + consumed = true; + } + } + return consumed; +} + +void uhf_scene_delete_on_exit(void* context) { + UHFApp* uhf_app = context; + + widget_reset(uhf_app->widget); +} diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_delete_success.c b/applications/external/uhf_rfid/scenes/uhf_scene_delete_success.c new file mode 100644 index 000000000..3fd3780cc --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_delete_success.c @@ -0,0 +1,40 @@ +#include "../uhf_app_i.h" + +void uhf_scene_delete_success_popup_callback(void* context) { + UHFApp* uhf_app = context; + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventViewExit); +} + +void uhf_scene_delete_success_on_enter(void* context) { + UHFApp* uhf_app = context; + + // Setup view + Popup* popup = uhf_app->popup; + popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); + popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, uhf_app); + popup_set_callback(popup, uhf_scene_delete_success_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup); +} + +bool uhf_scene_delete_success_on_event(void* context, SceneManagerEvent event) { + UHFApp* uhf_app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == UHFCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + uhf_app->scene_manager, UHFSceneStart); + } + } + return consumed; +} + +void uhf_scene_delete_success_on_exit(void* context) { + UHFApp* uhf_app = context; + + // Clear view + popup_reset(uhf_app->popup); +} diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_device_info.c b/applications/external/uhf_rfid/scenes/uhf_scene_device_info.c new file mode 100644 index 000000000..899957fb0 --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_device_info.c @@ -0,0 +1,133 @@ +#include "../uhf_app_i.h" +#include + +typedef enum { EPC_INFO, TID_INFO, USER_INFO } UHFTagInfo; + +static UHFTagInfo current_info; + +char* get_current_bank_info_str() { + switch(current_info) { + case EPC_INFO: + return "EPC Bank"; + case TID_INFO: + return "TID Bank"; + case USER_INFO: + return "User Bank"; + } + return ""; +} + +char* get_next_bank_info_str() { + switch(current_info) { + case EPC_INFO: + current_info = TID_INFO; + return "TID"; + case TID_INFO: + current_info = USER_INFO; + return "USER"; + case USER_INFO: + current_info = EPC_INFO; + return "EPC"; + } + return ""; +} + +void uhf_scene_device_info_widget_callback(GuiButtonType result, InputType type, void* context) { + UHFApp* uhf_app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result); + } +} + +void change_view_on_event(UHFApp* uhf_app) { + UHFTag* uhf_tag = uhf_app->uhf_device->uhf_tag_wrapper->uhf_tag; + FuriString* furi_temp_str; + furi_temp_str = furi_string_alloc(); + char* temp_str; + size_t length; + + widget_reset(uhf_app->widget); + widget_add_string_element( + uhf_app->widget, 64, 5, AlignCenter, AlignCenter, FontPrimary, get_current_bank_info_str()); + + switch(current_info) { + case EPC_INFO: + temp_str = convertToHexString(uhf_tag->epc->data, uhf_tag->epc->size); + length = uhf_tag->epc->size; + break; + case TID_INFO: + temp_str = convertToHexString(uhf_tag->tid->data, uhf_tag->tid->size); + length = uhf_tag->tid->size; + break; + case USER_INFO: + temp_str = convertToHexString(uhf_tag->user->data, uhf_tag->user->size); + length = uhf_tag->user->size; + break; + default: + temp_str = NULL; + length = 0; + break; + } + + furi_string_cat_printf(furi_temp_str, "Length: %d bytes", length); + + widget_add_string_element( + uhf_app->widget, + 3, + 12, + AlignLeft, + AlignTop, + FontKeyboard, + furi_string_get_cstr(furi_temp_str)); + + widget_add_string_multiline_element( + uhf_app->widget, 3, 24, AlignLeft, AlignTop, FontKeyboard, temp_str); + + widget_add_button_element( + uhf_app->widget, + GuiButtonTypeRight, + get_next_bank_info_str(), + uhf_scene_device_info_widget_callback, + uhf_app); + + widget_add_button_element( + uhf_app->widget, GuiButtonTypeLeft, "Back", uhf_scene_device_info_widget_callback, uhf_app); + + furi_string_free(furi_temp_str); + free(temp_str); +} + +void uhf_scene_device_info_on_enter(void* context) { + UHFApp* uhf_app = context; + current_info = EPC_INFO; + dolphin_deed(DolphinDeedNfcReadSuccess); + change_view_on_event(uhf_app); + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget); +} + +bool uhf_scene_device_info_on_event(void* context, SceneManagerEvent event) { + UHFApp* uhf_app = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeTick) return false; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(uhf_app->scene_manager); + } else if(event.event == GuiButtonTypeRight) { + change_view_on_event(uhf_app); + } else if(event.event == UHFCustomEventViewExit) { + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_previous_scene(uhf_app->scene_manager); + consumed = true; + } + return consumed; +} + +void uhf_scene_device_info_on_exit(void* context) { + UHFApp* uhf_app = context; + + // Clear views + widget_reset(uhf_app->widget); +} diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_file_select.c b/applications/external/uhf_rfid/scenes/uhf_scene_file_select.c new file mode 100644 index 000000000..ccecd78af --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_file_select.c @@ -0,0 +1,23 @@ +#include "../uhf_app_i.h" + +void uhf_scene_file_select_on_enter(void* context) { + UHFApp* uhf_app = context; + // Process file_select return + uhf_device_set_loading_callback(uhf_app->uhf_device, uhf_show_loading_popup, uhf_app); + if(uhf_file_select(uhf_app->uhf_device)) { + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSavedMenu); + } else { + scene_manager_search_and_switch_to_previous_scene(uhf_app->scene_manager, UHFSceneStart); + } + uhf_device_set_loading_callback(uhf_app->uhf_device, NULL, uhf_app); +} + +bool uhf_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void uhf_scene_file_select_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_read_tag.c b/applications/external/uhf_rfid/scenes/uhf_scene_read_tag.c new file mode 100644 index 000000000..4e1e98383 --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_read_tag.c @@ -0,0 +1,45 @@ +#include "../uhf_app_i.h" +#include + +void uhf_read_tag_worker_callback(UHFWorkerEvent event, void* ctx) { + UHFApp* uhf_app = ctx; + if(event == UHFWorkerEventSuccess) { + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventWorkerExit); + } +} + +void uhf_scene_read_tag_on_enter(void* ctx) { + UHFApp* uhf_app = ctx; + dolphin_deed(DolphinDeedNfcRead); + + // Setup view + Popup* popup = uhf_app->popup; + popup_set_header(popup, "Detecting\n[UHF] RFID\nTag", 68, 30, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61); + + // Start worker + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup); + uhf_worker_start( + uhf_app->worker, UHFWorkerStateDetectSingle, uhf_read_tag_worker_callback, uhf_app); + + uhf_blink_start(uhf_app); +} + +bool uhf_scene_read_tag_on_event(void* ctx, SceneManagerEvent event) { + UHFApp* uhf_app = ctx; + bool consumed = false; + if(event.event == UHFCustomEventWorkerExit) { + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneReadTagSuccess); + consumed = true; + } + return consumed; +} + +void uhf_scene_read_tag_on_exit(void* ctx) { + UHFApp* uhf_app = ctx; + // Stop worker + uhf_worker_stop(uhf_app->worker); + // Clear view + popup_reset(uhf_app->popup); + uhf_blink_stop(uhf_app); +} diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_read_tag_success.c b/applications/external/uhf_rfid/scenes/uhf_scene_read_tag_success.c new file mode 100644 index 000000000..1ab724f61 --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_read_tag_success.c @@ -0,0 +1,111 @@ +#include "../uhf_app_i.h" +#include + +void uhf_read_tag_success_worker_callback(UHFWorkerEvent event, void* ctx) { + UNUSED(event); + UNUSED(ctx); +} + +void uhf_scene_read_card_success_widget_callback(GuiButtonType result, InputType type, void* ctx) { + furi_assert(ctx); + UHFApp* uhf_app = ctx; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result); + } +} + +void uhf_scene_read_tag_success_on_enter(void* ctx) { + UHFApp* uhf_app = ctx; + UHFTag* uhf_tag = uhf_app->worker->uhf_tag_wrapper->uhf_tag; + FuriString* temp_str = furi_string_alloc(); + + dolphin_deed(DolphinDeedNfcReadSuccess); + + // Send notification + notification_message(uhf_app->notifications, &sequence_success); + + widget_add_string_element( + uhf_app->widget, 32, 5, AlignLeft, AlignCenter, FontPrimary, "Read Success"); + + widget_add_string_element(uhf_app->widget, 3, 18, AlignLeft, AlignCenter, FontPrimary, "PC :"); + + widget_add_string_element( + uhf_app->widget, 66, 18, AlignLeft, AlignCenter, FontPrimary, "CRC :"); + + widget_add_string_element( + uhf_app->widget, 3, 32, AlignLeft, AlignCenter, FontPrimary, "EPC :"); + + furi_string_cat_printf(temp_str, "%04X", uhf_tag->epc->pc); + widget_add_string_element( + uhf_app->widget, + 26, + 19, + AlignLeft, + AlignCenter, + FontKeyboard, + furi_string_get_cstr(temp_str)); + furi_string_reset(temp_str); + furi_string_cat_printf(temp_str, "%04X", uhf_tag->epc->crc); + widget_add_string_element( + uhf_app->widget, + 96, + 19, + AlignLeft, + AlignCenter, + FontKeyboard, + furi_string_get_cstr(temp_str)); + char* epc = convertToHexString(uhf_tag->epc->data, uhf_tag->epc->size); + if(epc != NULL) { + widget_add_string_multiline_element( + uhf_app->widget, 34, 29, AlignLeft, AlignTop, FontKeyboard, epc); + } + widget_add_button_element( + uhf_app->widget, + GuiButtonTypeRight, + "More", + uhf_scene_read_card_success_widget_callback, + uhf_app); + widget_add_button_element( + uhf_app->widget, + GuiButtonTypeLeft, + "Exit", + uhf_scene_read_card_success_widget_callback, + uhf_app); + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget); + free(epc); + furi_string_free(temp_str); +} + +bool uhf_scene_read_tag_success_on_event(void* ctx, SceneManagerEvent event) { + UHFApp* uhf_app = ctx; + bool consumed = false; + if(event.event == SceneManagerEventTypeBack) { + uhf_app->worker->state = UHFWorkerStateStop; + } + if(event.type == SceneManagerEventTypeCustom) { + // if 'exit' is pressed go back to home screen + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_search_and_switch_to_previous_scene( + uhf_app->scene_manager, UHFSceneStart); + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneTagMenu); + consumed = true; + } else if(event.event == GuiButtonTypeCenter) { + // consumed = scene_manager_search_and_switch_to_another_scene( + // picopass->scene_manager, PicopassSceneStart); + } + } + return consumed; +} + +void uhf_scene_read_tag_success_on_exit(void* ctx) { + UHFApp* uhf_app = ctx; + + // // Stop worker + uhf_worker_stop(uhf_app->worker); + // Clear view + popup_reset(uhf_app->popup); + // clear widget + widget_reset(uhf_app->widget); +} \ No newline at end of file diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_save_name.c b/applications/external/uhf_rfid/scenes/uhf_scene_save_name.c new file mode 100644 index 000000000..3fa9402d3 --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_save_name.c @@ -0,0 +1,74 @@ +#include "../uhf_app_i.h" +#include +#include +#include + +void uhf_scene_save_name_text_input_callback(void* context) { + UHFApp* uhf_app = context; + + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventTextInputDone); +} + +void uhf_scene_save_name_on_enter(void* context) { + UHFApp* uhf_app = context; + + // Setup view + TextInput* text_input = uhf_app->text_input; + name_generator_make_random(uhf_app->text_store, sizeof(uhf_app->text_store)); + text_input_set_header_text(text_input, "Name the tag"); + text_input_set_result_callback( + text_input, + uhf_scene_save_name_text_input_callback, + uhf_app, + uhf_app->text_store, + UHF_DEV_NAME_MAX_LEN, + true); + + FuriString* folder_path; + folder_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX); + + if(furi_string_end_with(uhf_app->uhf_device->load_path, UHF_APP_EXTENSION)) { + path_extract_dirname(furi_string_get_cstr(uhf_app->uhf_device->load_path), folder_path); + } + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + furi_string_get_cstr(folder_path), UHF_APP_EXTENSION, uhf_app->uhf_device->dev_name); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewTextInput); + + furi_string_free(folder_path); +} + +bool uhf_scene_save_name_on_event(void* context, SceneManagerEvent event) { + UHFApp* uhf_app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == UHFCustomEventTextInputDone) { + strlcpy( + uhf_app->uhf_device->dev_name, + uhf_app->text_store, + strlen(uhf_app->text_store) + 1); + if(uhf_device_save(uhf_app->uhf_device, uhf_app->text_store)) { + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSaveSuccess); + consumed = true; + } else { + consumed = scene_manager_search_and_switch_to_previous_scene( + uhf_app->scene_manager, UHFSceneStart); + } + } + } + return consumed; +} + +void uhf_scene_save_name_on_exit(void* context) { + UHFApp* uhf_app = context; + + // Clear view + void* validator_context = text_input_get_validator_callback_context(uhf_app->text_input); + text_input_set_validator(uhf_app->text_input, NULL, NULL); + validator_is_file_free(validator_context); + + text_input_reset(uhf_app->text_input); +} diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_save_success.c b/applications/external/uhf_rfid/scenes/uhf_scene_save_success.c new file mode 100644 index 000000000..69dc0b296 --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_save_success.c @@ -0,0 +1,47 @@ +#include "../uhf_app_i.h" +#include + +void uhf_scene_save_success_popup_callback(void* context) { + UHFApp* uhf_app = context; + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventViewExit); +} + +void uhf_scene_save_success_on_enter(void* context) { + UHFApp* uhf_app = context; + dolphin_deed(DolphinDeedNfcSave); + + // Setup view + Popup* popup = uhf_app->popup; + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, uhf_app); + popup_set_callback(popup, uhf_scene_save_success_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup); +} + +bool uhf_scene_save_success_on_event(void* context, SceneManagerEvent event) { + UHFApp* uhf_app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == UHFCustomEventViewExit) { + if(scene_manager_has_previous_scene(uhf_app->scene_manager, UHFSceneTagMenu)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + uhf_app->scene_manager, UHFSceneTagMenu); + } else { + consumed = scene_manager_search_and_switch_to_previous_scene( + uhf_app->scene_manager, UHFSceneStart); + } + } + } + return consumed; +} + +void uhf_scene_save_success_on_exit(void* context) { + UHFApp* uhf_app = context; + + // Clear view + popup_reset(uhf_app->popup); +} diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_saved_menu.c b/applications/external/uhf_rfid/scenes/uhf_scene_saved_menu.c new file mode 100644 index 000000000..c06514ced --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_saved_menu.c @@ -0,0 +1,59 @@ +#include "../uhf_app_i.h" + +enum SubmenuIndex { + SubmenuIndexDelete, + SubmenuIndexInfo, + SubmenuIndexWrite, +}; + +void uhf_scene_saved_menu_submenu_callback(void* context, uint32_t index) { + UHFApp* uhf_app = context; + + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, index); +} + +void uhf_scene_saved_menu_on_enter(void* context) { + UHFApp* uhf_app = context; + Submenu* submenu = uhf_app->submenu; + + submenu_add_item( + submenu, "Delete", SubmenuIndexDelete, uhf_scene_saved_menu_submenu_callback, uhf_app); + submenu_add_item( + submenu, "Info", SubmenuIndexInfo, uhf_scene_saved_menu_submenu_callback, uhf_app); + submenu_add_item( + submenu, "Write", SubmenuIndexWrite, uhf_scene_saved_menu_submenu_callback, uhf_app); + + submenu_set_selected_item( + uhf_app->submenu, + scene_manager_get_scene_state(uhf_app->scene_manager, UHFSceneSavedMenu)); + + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewMenu); +} + +bool uhf_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { + UHFApp* uhf_app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(uhf_app->scene_manager, UHFSceneSavedMenu, event.event); + + if(event.event == SubmenuIndexDelete) { + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneDelete); + consumed = true; + } else if(event.event == SubmenuIndexInfo) { + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneDeviceInfo); + consumed = true; + } else if(event.event == SubmenuIndexWrite) { + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneWriteTag); + consumed = true; + } + } + + return consumed; +} + +void uhf_scene_saved_menu_on_exit(void* context) { + UHFApp* uhf_app = context; + + submenu_reset(uhf_app->submenu); +} diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_settings.c b/applications/external/uhf_rfid/scenes/uhf_scene_settings.c new file mode 100644 index 000000000..f1cb30bcc --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_settings.c @@ -0,0 +1,122 @@ +#include "../uhf_app_i.h" +#include "../uhf_module.h" + +void uhf_settings_set_module_baudrate(VariableItem* item) { + M100Module* uhf_module = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + if(index >= BAUD_RATES_COUNT) { + return; + } + uint32_t baudrate = BAUD_RATES[index]; + m100_set_baudrate(uhf_module, baudrate); + char text_buf[10]; + snprintf(text_buf, sizeof(text_buf), "%lu", uhf_module->baudrate); + variable_item_set_current_value_text(item, text_buf); +} + +void uhf_settings_set_module_powerdb(VariableItem* item) { + M100Module* uhf_module = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + if(index >= POWER_DBM_COUNT) { + return; + } + uint16_t power = POWER_DBM[index]; + m100_set_transmitting_power(uhf_module, power); + char text_buf[10]; + snprintf(text_buf, sizeof(text_buf), "%ddBm", uhf_module->transmitting_power); + variable_item_set_current_value_text(item, text_buf); +} + +void uhf_settings_set_module_working_region(VariableItem* item) { + M100Module* uhf_module = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + if(index >= WORKING_REGIONS_COUNT) { + return; + } + WorkingRegion region = WORKING_REGIONS[index]; + m100_set_working_region(uhf_module, region); + variable_item_set_current_value_text(item, WORKING_REGIONS_STR[index]); +} + +uint8_t uhf_settings_get_module_baudrate_index(M100Module* module) { + for(uint8_t i = 0; i < BAUD_RATES_COUNT; i++) { + if(BAUD_RATES[i] == module->baudrate) { + return i; + } + } + return 0; +} + +uint8_t uhf_settings_get_module_power_index(M100Module* module) { + for(uint8_t i = 0; i < BAUD_RATES_COUNT; i++) { + if(POWER_DBM[i] == module->transmitting_power) { + return i; + } + } + return 0; +} + +uint8_t uhf_settings_get_module_working_region_index(M100Module* module) { + for(uint8_t i = 0; i < WORKING_REGIONS_COUNT; i++) { + if(WORKING_REGIONS[i] == module->region) { + return i; + } + } + return 0; +} + +void uhf_scene_settings_on_enter(void* ctx) { + UHFApp* uhf_app = ctx; + M100Module* uhf_module = uhf_app->worker->module; + VariableItem* item; + VariableItemList* variable_item_list = uhf_app->variable_item_list; + + uint8_t value_index = uhf_settings_get_module_baudrate_index(uhf_module); + char text_buf[10]; + snprintf(text_buf, sizeof(text_buf), "%lu", uhf_module->baudrate); + item = variable_item_list_add( + variable_item_list, + "Baudrate:", + BAUD_RATES_COUNT, + uhf_settings_set_module_baudrate, + uhf_module); + + variable_item_set_current_value_text(item, text_buf); + variable_item_set_current_value_index(item, value_index); + + value_index = uhf_settings_get_module_power_index(uhf_module); + item = variable_item_list_add( + variable_item_list, + "Power(DBM):", + POWER_DBM_COUNT, + uhf_settings_set_module_powerdb, + uhf_module); + snprintf(text_buf, sizeof(text_buf), "%ddBm", uhf_module->transmitting_power); + variable_item_set_current_value_text(item, text_buf); + variable_item_set_current_value_index(item, value_index); + + value_index = uhf_settings_get_module_working_region_index(uhf_module); + item = variable_item_list_add( + variable_item_list, + "Region:", + WORKING_REGIONS_COUNT, + uhf_settings_set_module_working_region, + uhf_module); + variable_item_set_current_value_text(item, WORKING_REGIONS_STR[value_index]); + variable_item_set_current_value_index(item, value_index); + + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewVariableItemList); +} + +bool uhf_scene_settings_on_event(void* ctx, SceneManagerEvent event) { + UHFApp* uhf_app = ctx; + UNUSED(uhf_app); + UNUSED(event); + return false; +} + +void uhf_scene_settings_on_exit(void* ctx) { + UHFApp* uhf_app = ctx; + variable_item_list_set_selected_item(uhf_app->variable_item_list, 0); + variable_item_list_reset(uhf_app->variable_item_list); +} \ No newline at end of file diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_start.c b/applications/external/uhf_rfid/scenes/uhf_scene_start.c new file mode 100644 index 000000000..a38122975 --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_start.c @@ -0,0 +1,54 @@ +#include "../uhf_app_i.h" + +enum SubmenuIndex { SubmenuIndexRead, SubmenuIndexSaved, SubmenuIndexSettings }; + +void uhf_scene_start_submenu_callback(void* ctx, uint32_t index) { + UHFApp* uhf_app = ctx; + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, index); +} + +void uhf_scene_start_on_enter(void* ctx) { + UHFApp* uhf_app = ctx; + + Submenu* submenu = uhf_app->submenu; + submenu_add_item( + submenu, "Read Tag", SubmenuIndexRead, uhf_scene_start_submenu_callback, uhf_app); + submenu_add_item( + submenu, "Saved", SubmenuIndexSaved, uhf_scene_start_submenu_callback, uhf_app); + submenu_add_item( + submenu, "Settings", SubmenuIndexSettings, uhf_scene_start_submenu_callback, uhf_app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(uhf_app->scene_manager, UHFSceneStart)); + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewMenu); +} + +bool uhf_scene_start_on_event(void* ctx, SceneManagerEvent event) { + UHFApp* uhf_app = ctx; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexRead) { + scene_manager_set_scene_state(uhf_app->scene_manager, UHFSceneStart, SubmenuIndexRead); + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneReadTag); + consumed = true; + } else if(event.event == SubmenuIndexSaved) { + // Explicitly save state so that the correct item is + // reselected if the user cancels loading a file. + scene_manager_set_scene_state( + uhf_app->scene_manager, UHFSceneStart, SubmenuIndexSaved); + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneFileSelect); + consumed = true; + } else if(event.event == SubmenuIndexSettings) { + scene_manager_set_scene_state( + uhf_app->scene_manager, UHFSceneStart, SubmenuIndexSettings); + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSettings); + consumed = true; + } + } + return consumed; +} + +void uhf_scene_start_on_exit(void* ctx) { + UHFApp* uhf_app = ctx; + submenu_reset(uhf_app->submenu); +} \ No newline at end of file diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_tag_menu.c b/applications/external/uhf_rfid/scenes/uhf_scene_tag_menu.c new file mode 100644 index 000000000..194677d91 --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_tag_menu.c @@ -0,0 +1,58 @@ +#include "../uhf_app_i.h" + +enum SubmenuIndex { + SubmenuIndexSave, + SubmenuIndexChangeKey, +}; + +void uhf_scene_tag_menu_submenu_callback(void* ctx, uint32_t index) { + UHFApp* uhf_app = ctx; + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, index); +} + +void uhf_scene_tag_menu_on_enter(void* ctx) { + UHFApp* uhf_app = ctx; + + Submenu* submenu = uhf_app->submenu; + + submenu_add_item( + submenu, "Save", SubmenuIndexSave, uhf_scene_tag_menu_submenu_callback, uhf_app); + submenu_add_item( + submenu, "Change Key", SubmenuIndexChangeKey, uhf_scene_tag_menu_submenu_callback, uhf_app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(uhf_app->scene_manager, UHFSceneTagMenu)); + + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewMenu); +} + +bool uhf_scene_tag_menu_on_event(void* ctx, SceneManagerEvent event) { + UHFApp* uhf_app = ctx; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSave) { + scene_manager_set_scene_state( + uhf_app->scene_manager, UHFSceneTagMenu, SubmenuIndexSave); + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSaveName); + consumed = true; + } + // else if(event.event == SubmenuIndexChangeKey) { + // scene_manager_set_scene_state( + // picopass->scene_manager, UHFSceneTagMenu, SubmenuIndexChangeKey); + // scene_manager_next_scene(picopass->scene_manager, PicopassSceneKeyMenu); + // consumed = true; + // } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + uhf_app->scene_manager, UHFSceneStart); + } + + return consumed; +} + +void uhf_scene_tag_menu_on_exit(void* ctx) { + UHFApp* uhf_app = ctx; + + submenu_reset(uhf_app->submenu); +} diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_verify.c b/applications/external/uhf_rfid/scenes/uhf_scene_verify.c new file mode 100644 index 000000000..74101beca --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_verify.c @@ -0,0 +1,147 @@ +#include "../uhf_app_i.h" + +bool verify_success = false; +FuriString* temp_str; + +void uhf_scene_verify_callback_event(UHFWorkerEvent event, void* ctx) { + UNUSED(ctx); + UHFApp* uhf_app = ctx; + if(event == UHFWorkerEventSuccess) verify_success = true; + + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventVerifyDone); +} + +void uhf_scene_verify_widget_callback(GuiButtonType result, InputType type, void* ctx) { + furi_assert(ctx); + UHFApp* uhf_app = ctx; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result); + } +} + +void uhf_scene_verify_on_enter(void* ctx) { + UHFApp* uhf_app = ctx; + uhf_worker_start( + uhf_app->worker, UHFWorkerStateVerify, uhf_scene_verify_callback_event, uhf_app); + temp_str = furi_string_alloc(); + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget); +} + +bool uhf_scene_verify_on_event(void* ctx, SceneManagerEvent event) { + UHFApp* uhf_app = ctx; + bool consumed = false; + if(event.event == SceneManagerEventTypeBack) { + uhf_app->worker->state = UHFWorkerStateStop; + } else if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneStart); + consumed = true; + } else if(event.event == GuiButtonTypeLeft) { + if(!verify_success) { + widget_reset(uhf_app->widget); + furi_string_reset(temp_str); + uhf_worker_stop(uhf_app->worker); + // furi_hal_gpio_write(&gpio_ext, false); + // furi_delay_ms(50); + // furi_hal_gpio_write(&gpio_ext_pa7, true); + // furi_delay_ms(50); + uhf_worker_start( + uhf_app->worker, + UHFWorkerStateVerify, + uhf_scene_verify_callback_event, + uhf_app); + } + } else if(event.event == UHFCustomEventVerifyDone) { + if(verify_success) { + widget_reset(uhf_app->widget); + furi_string_reset(temp_str); + M100Module* module = uhf_app->worker->module; + widget_add_string_element( + uhf_app->widget, 64, 5, AlignCenter, AlignCenter, FontPrimary, "Module Info"); + // hardware info + furi_string_cat_str(temp_str, "HW Version: "); + furi_string_cat_str(temp_str, module->info->hw_version); + widget_add_string_element( + uhf_app->widget, + 1, + 15, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + furi_string_reset(temp_str); + // software info + furi_string_cat_str(temp_str, "SW Version: "); + furi_string_cat_str(temp_str, module->info->sw_version); + widget_add_string_element( + uhf_app->widget, + 1, + 27, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + furi_string_reset(temp_str); + // manufacturer info + furi_string_cat_str(temp_str, "Manufacturer: "); + furi_string_cat_str(temp_str, module->info->manufacturer); + widget_add_string_element( + uhf_app->widget, + 1, + 39, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + + widget_add_button_element( + uhf_app->widget, + GuiButtonTypeRight, + "Continue", + uhf_scene_verify_widget_callback, + uhf_app); + } else { + widget_add_string_element( + uhf_app->widget, + 64, + 5, + AlignCenter, + AlignCenter, + FontPrimary, + "No UHF Module found"); + widget_add_string_multiline_element( + uhf_app->widget, + 64, + 30, + AlignCenter, + AlignCenter, + FontSecondary, + "Please refer to the git@frux-c/uhf_rfid for help."); + widget_add_button_element( + uhf_app->widget, + GuiButtonTypeLeft, + "Retry", + uhf_scene_verify_widget_callback, + uhf_app); + widget_add_button_element( + uhf_app->widget, + GuiButtonTypeRight, + "Skip", + uhf_scene_verify_widget_callback, + uhf_app); + } + } + } + return consumed; +} + +void uhf_scene_verify_on_exit(void* ctx) { + UHFApp* uhf_app = ctx; + // Clear string + furi_string_free(temp_str); + // Stop worker + uhf_worker_stop(uhf_app->worker); + // clear widget + widget_reset(uhf_app->widget); +} \ No newline at end of file diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_write_tag.c b/applications/external/uhf_rfid/scenes/uhf_scene_write_tag.c new file mode 100644 index 000000000..57c548da9 --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_write_tag.c @@ -0,0 +1,49 @@ +#include "../uhf_app_i.h" +#include + +void uhf_write_tag_worker_callback(UHFWorkerEvent event, void* ctx) { + UHFApp* uhf_app = ctx; + if(event == UHFWorkerEventSuccess) { + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventWorkerExit); + } + // } else if(event == UHFWorkerEventAborted) { + // scene_manager_search_and_switch_to_previous_scene(uhf_app->scene_manager, UHFSceneStart); + // } +} + +void uhf_scene_write_tag_on_enter(void* ctx) { + UHFApp* uhf_app = ctx; + dolphin_deed(DolphinDeedNfcRead); + + // Setup view + Popup* popup = uhf_app->popup; + popup_set_header(popup, "Writing\n[UHF] RFID\nTag", 68, 30, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); + + // Start worker + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup); + uhf_worker_start( + uhf_app->worker, UHFWorkerStateWriteSingle, uhf_write_tag_worker_callback, uhf_app); + + uhf_blink_start(uhf_app); +} + +bool uhf_scene_write_tag_on_event(void* ctx, SceneManagerEvent event) { + UHFApp* uhf_app = ctx; + bool consumed = false; + if(event.event == UHFCustomEventWorkerExit) { + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneWriteTagSuccess); + consumed = true; + } + return consumed; +} + +void uhf_scene_write_tag_on_exit(void* ctx) { + UHFApp* uhf_app = ctx; + // Stop worker + uhf_worker_stop(uhf_app->worker); + // Clear view + popup_reset(uhf_app->popup); + + uhf_blink_stop(uhf_app); +} diff --git a/applications/external/uhf_rfid/scenes/uhf_scene_write_tag_success.c b/applications/external/uhf_rfid/scenes/uhf_scene_write_tag_success.c new file mode 100644 index 000000000..6148e5125 --- /dev/null +++ b/applications/external/uhf_rfid/scenes/uhf_scene_write_tag_success.c @@ -0,0 +1,94 @@ +#include "../uhf_app_i.h" +#include + +void uhf_write_tag_success_worker_callback(UHFWorkerEvent event, void* ctx) { + UNUSED(event); + UNUSED(ctx); +} + +void uhf_scene_write_tag_success_widget_callback(GuiButtonType result, InputType type, void* ctx) { + furi_assert(ctx); + UHFApp* uhf_app = ctx; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result); + } +} + +void uhf_scene_write_tag_success_on_enter(void* ctx) { + UHFApp* uhf_app = ctx; + + dolphin_deed(DolphinDeedNfcReadSuccess); + + // Send notification + notification_message(uhf_app->notifications, &sequence_success); + + widget_add_string_element( + uhf_app->widget, 32, 5, AlignLeft, AlignCenter, FontPrimary, "Write Success"); + + // widget_add_string_element(uhf_app->widget, 3, 18, AlignLeft, AlignCenter, FontPrimary, "PC :"); + + // widget_add_string_element( + // uhf_app->widget, 66, 18, AlignLeft, AlignCenter, FontPrimary, "CRC :"); + + // widget_add_string_element( + // uhf_app->widget, 3, 32, AlignLeft, AlignCenter, FontPrimary, "EPC :"); + + // char* pc = convertToHexString(uhf_tag->pc, 2); + // widget_add_string_element(uhf_app->widget, 26, 19, AlignLeft, AlignCenter, FontKeyboard, pc); + // char* crc = convertToHexString(uhf_tag->crc, 2); + // widget_add_string_element(uhf_app->widget, 96, 19, AlignLeft, AlignCenter, FontKeyboard, crc); + // char* epc = convertToHexString(uhf_tag->epc + 2, uhf_tag->epc_length - 2); + // widget_add_string_multiline_element( + // uhf_app->widget, 34, 29, AlignLeft, AlignTop, FontKeyboard, epc); + + widget_add_button_element( + uhf_app->widget, + GuiButtonTypeRight, + "More", + uhf_scene_write_tag_success_widget_callback, + uhf_app); + widget_add_button_element( + uhf_app->widget, + GuiButtonTypeLeft, + "Exit", + uhf_scene_write_tag_success_widget_callback, + uhf_app); + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget); + // free(pc); + // free(crc); + // free(epc); +} + +bool uhf_scene_write_tag_success_on_event(void* ctx, SceneManagerEvent event) { + UHFApp* uhf_app = ctx; + bool consumed = false; + if(event.event == SceneManagerEventTypeBack) { + uhf_app->worker->state = UHFWorkerStateStop; + } + if(event.type == SceneManagerEventTypeCustom) { + // if 'exit' is pressed go back to home screen + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_search_and_switch_to_previous_scene( + uhf_app->scene_manager, UHFSceneStart); + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneTagMenu); + consumed = true; + } else if(event.event == GuiButtonTypeCenter) { + // consumed = scene_manager_search_and_switch_to_another_scene( + // picopass->scene_manager, PicopassSceneStart); + } + } + return consumed; +} + +void uhf_scene_write_tag_success_on_exit(void* ctx) { + UHFApp* uhf_app = ctx; + + // // Stop worker + uhf_worker_stop(uhf_app->worker); + // Clear view + popup_reset(uhf_app->popup); + // clear widget + widget_reset(uhf_app->widget); +} \ No newline at end of file diff --git a/applications/external/uhf_rfid/uhf_10px.png b/applications/external/uhf_rfid/uhf_10px.png new file mode 100644 index 000000000..aa9a29f89 Binary files /dev/null and b/applications/external/uhf_rfid/uhf_10px.png differ diff --git a/applications/external/uhf_rfid/uhf_app.c b/applications/external/uhf_rfid/uhf_app.c new file mode 100644 index 000000000..2e6610a98 --- /dev/null +++ b/applications/external/uhf_rfid/uhf_app.c @@ -0,0 +1,214 @@ +#include "uhf_app_i.h" + +char* convertToHexString(uint8_t* array, size_t length) { + if(array == NULL || length == 0) { + return " "; + } + FuriString* temp_str = furi_string_alloc(); + + for(size_t i = 0; i < length; i++) { + furi_string_cat_printf(temp_str, "%02X ", array[i]); + } + const char* furi_str = furi_string_get_cstr(temp_str); + + size_t str_len = strlen(furi_str); + char* str = (char*)malloc(sizeof(char) * str_len); + + memcpy(str, furi_str, str_len); + furi_string_free(temp_str); + return str; +} + +bool uhf_custom_event_callback(void* ctx, uint32_t event) { + furi_assert(ctx); + UHFApp* uhf_app = ctx; + return scene_manager_handle_custom_event(uhf_app->scene_manager, event); +} + +bool uhf_back_event_callback(void* ctx) { + furi_assert(ctx); + UHFApp* uhf_app = ctx; + return scene_manager_handle_back_event(uhf_app->scene_manager); +} + +void uhf_tick_event_callback(void* ctx) { + furi_assert(ctx); + UHFApp* uhf_app = ctx; + scene_manager_handle_tick_event(uhf_app->scene_manager); +} + +UHFApp* uhf_alloc() { + UHFApp* uhf_app = (UHFApp*)malloc(sizeof(UHFApp)); + uhf_app->view_dispatcher = view_dispatcher_alloc(); + uhf_app->scene_manager = scene_manager_alloc(&uhf_scene_handlers, uhf_app); + view_dispatcher_enable_queue(uhf_app->view_dispatcher); + view_dispatcher_set_event_callback_context(uhf_app->view_dispatcher, uhf_app); + view_dispatcher_set_custom_event_callback(uhf_app->view_dispatcher, uhf_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + uhf_app->view_dispatcher, uhf_back_event_callback); + view_dispatcher_set_tick_event_callback( + uhf_app->view_dispatcher, uhf_tick_event_callback, 100); + + // Open GUI record + uhf_app->gui = furi_record_open(RECORD_GUI); + view_dispatcher_attach_to_gui( + uhf_app->view_dispatcher, uhf_app->gui, ViewDispatcherTypeFullscreen); + + // Variable Item List + uhf_app->variable_item_list = variable_item_list_alloc(); + + //worker + uhf_app->worker = uhf_worker_alloc(); + + // device + uhf_app->uhf_device = uhf_device_alloc(); + + UHFTagWrapper* uhf_tag_wrapper = uhf_tag_wrapper_alloc(); + + // // point tag object to worker + uhf_app->worker->uhf_tag_wrapper = uhf_tag_wrapper; + uhf_app->uhf_device->uhf_tag_wrapper = uhf_tag_wrapper; + + // Open Notification record + uhf_app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // Variable Item List + uhf_app->variable_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + uhf_app->view_dispatcher, + UHFViewVariableItemList, + variable_item_list_get_view(uhf_app->variable_item_list)); + + // Submenu + uhf_app->submenu = submenu_alloc(); + view_dispatcher_add_view( + uhf_app->view_dispatcher, UHFViewMenu, submenu_get_view(uhf_app->submenu)); + + // Popup + uhf_app->popup = popup_alloc(); + view_dispatcher_add_view( + uhf_app->view_dispatcher, UHFViewPopup, popup_get_view(uhf_app->popup)); + + // Loading + uhf_app->loading = loading_alloc(); + view_dispatcher_add_view( + uhf_app->view_dispatcher, UHFViewLoading, loading_get_view(uhf_app->loading)); + + // Text Input + uhf_app->text_input = text_input_alloc(); + view_dispatcher_add_view( + uhf_app->view_dispatcher, UHFViewTextInput, text_input_get_view(uhf_app->text_input)); + + // Custom Widget + uhf_app->widget = widget_alloc(); + view_dispatcher_add_view( + uhf_app->view_dispatcher, UHFViewWidget, widget_get_view(uhf_app->widget)); + + return uhf_app; +} + +void uhf_free(UHFApp* uhf_app) { + furi_assert(uhf_app); + + // Submenu + view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewMenu); + submenu_free(uhf_app->submenu); + + // Popup + view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewPopup); + popup_free(uhf_app->popup); + + // Loading + view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewLoading); + loading_free(uhf_app->loading); + + // TextInput + view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewTextInput); + text_input_free(uhf_app->text_input); + + // Custom Widget + view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewWidget); + widget_free(uhf_app->widget); + + // Tag + uhf_tag_wrapper_free(uhf_app->worker->uhf_tag_wrapper); + + // Worker + uhf_worker_stop(uhf_app->worker); + uhf_worker_free(uhf_app->worker); + + // Device + uhf_device_free(uhf_app->uhf_device); + + // View Dispatcher + view_dispatcher_free(uhf_app->view_dispatcher); + + // Scene Manager + scene_manager_free(uhf_app->scene_manager); + + // GUI + furi_record_close(RECORD_GUI); + uhf_app->gui = NULL; + + // Variable Item List + variable_item_list_free(uhf_app->variable_item_list); + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + uhf_app->notifications = NULL; + + free(uhf_app); +} + +static const NotificationSequence uhf_sequence_blink_start_cyan = { + &message_blink_start_10, + &message_blink_set_color_cyan, + &message_do_not_reset, + NULL, +}; + +static const NotificationSequence uhf_sequence_blink_stop = { + &message_blink_stop, + NULL, +}; + +void uhf_blink_start(UHFApp* uhf_app) { + notification_message(uhf_app->notifications, &uhf_sequence_blink_start_cyan); +} + +void uhf_blink_stop(UHFApp* uhf_app) { + notification_message(uhf_app->notifications, &uhf_sequence_blink_stop); +} + +void uhf_show_loading_popup(void* ctx, bool show) { + UHFApp* uhf_app = ctx; + + if(show) { + // Raise timer priority so that animations can play + furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated); + view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewLoading); + } else { + // Restore default timer priority + furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal); + } +} + +int32_t uhf_app_main(void* ctx) { + UNUSED(ctx); + UHFApp* uhf_app = uhf_alloc(); + + // enable 5v pin + furi_hal_power_enable_otg(); + // init pin a2 + // furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull); + furi_hal_uart_set_br(FuriHalUartIdUSART1, DEFAULT_BAUDRATE); + scene_manager_next_scene(uhf_app->scene_manager, UHFSceneVerify); + view_dispatcher_run(uhf_app->view_dispatcher); + + // disable 5v pin + furi_hal_power_disable_otg(); + // furi_hal_gpio_disable_int_callback() + // exit app + uhf_free(uhf_app); + return 0; +} \ No newline at end of file diff --git a/applications/external/uhf_rfid/uhf_app.h b/applications/external/uhf_rfid/uhf_app.h new file mode 100644 index 000000000..651386585 --- /dev/null +++ b/applications/external/uhf_rfid/uhf_app.h @@ -0,0 +1,3 @@ +#pragma once + +typedef struct UHFApp UHFApp; diff --git a/applications/external/uhf_rfid/uhf_app_i.h b/applications/external/uhf_rfid/uhf_app_i.h new file mode 100644 index 000000000..d8bc03c91 --- /dev/null +++ b/applications/external/uhf_rfid/uhf_app_i.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "uhf_app.h" +#include "uhf_worker.h" +#include "uhf_device.h" +#include "scenes/uhf_scene.h" + +#include +#include +#include +#include + +#include + +#include + +#define UHF_TEXT_STORE_SIZE 128 +// #define UHF_APPS_DATA_FOLDER EXT_PATH("apps_data") +// #define UHF_APPS_STORAGE_FOLDER +// UHF_APPS_DATA_FOLDER "/" +// "uhf_rfid" +// #define UHF_FILE_EXTENSION ".uhf" + +enum UHFCustomEvent { + // Reserve first 100 events for button types and indexes, starting from 0 + UHFCustomEventReserved = 100, + + UHFCustomEventVerifyDone, + UHFCustomEventViewExit, + UHFCustomEventWorkerExit, + UHFCustomEventByteInputDone, + UHFCustomEventTextInputDone, + UHFCustomEventSceneSettingLock, +}; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +struct UHFApp { + UHFWorker* worker; + ViewDispatcher* view_dispatcher; + Gui* gui; + NotificationApp* notifications; + SceneManager* scene_manager; + VariableItemList* variable_item_list; + // Storage* storage; + UHFDevice* uhf_device; + char text_store[UHF_TEXT_STORE_SIZE + 1]; + FuriString* text_box_store; + // Common Views + Submenu* submenu; + Popup* popup; + Loading* loading; + TextInput* text_input; + Widget* widget; +}; + +typedef enum { + UHFViewMenu, + UHFViewPopup, + UHFViewLoading, + UHFViewTextInput, + UHFViewWidget, + UHFViewVariableItemList, +} UHFView; + +UHFApp* uhf_app_alloc(); + +void uhf_text_store_set(UHFApp* uhf, const char* text, ...); + +void uhf_text_store_clear(UHFApp* uhf); + +void uhf_blink_start(UHFApp* uhf); + +void uhf_blink_stop(UHFApp* uhf); + +void uhf_show_loading_popup(void* context, bool show); + +/** Check if memory is set to pattern + * + * @warning zero size will return false + * + * @param[in] data Pointer to the byte array + * @param[in] pattern The pattern + * @param[in] size The byte array size + * + * @return True if memory is set to pattern, false otherwise + */ +bool uhf_is_memset(const uint8_t* data, const uint8_t pattern, size_t size); + +char* convertToHexString(uint8_t* array, size_t length); + +// bool uhf_save_read_data(UHFResponseData* uhf_response_data, Storage* storage, const char* filename); diff --git a/applications/external/uhf_rfid/uhf_buffer.c b/applications/external/uhf_rfid/uhf_buffer.c new file mode 100644 index 000000000..8e56f88df --- /dev/null +++ b/applications/external/uhf_rfid/uhf_buffer.c @@ -0,0 +1,69 @@ +#include "uhf_buffer.h" +#include +#include + +Buffer* buffer_alloc(size_t initial_capacity) { + Buffer* buf = (Buffer*)malloc(sizeof(Buffer)); + buf->data = (uint8_t*)malloc(sizeof(uint8_t) * initial_capacity); + if(!buf->data) { + free(buf); + return NULL; + } + buf->size = 0; + buf->capacity = initial_capacity; + return buf; +} + +bool buffer_append_single(Buffer* buf, uint8_t data) { + if(buf->closed) return false; + if(buf->size + 1 > buf->capacity) { + size_t new_capacity = buf->capacity * 2; + uint8_t* new_data = (uint8_t*)realloc(buf->data, sizeof(uint8_t) * new_capacity); + if(!new_data) return false; + buf->data = new_data; + buf->capacity = new_capacity; + } + buf->data[buf->size++] = data; + return true; +} + +bool buffer_append(Buffer* buf, uint8_t* data, size_t data_size) { + if(buf->closed) return false; + if(buf->size + data_size > buf->capacity) { + size_t new_capacity = buf->capacity * 2; + uint8_t* new_data = (uint8_t*)realloc(buf->data, new_capacity); + if(!new_data) return false; + + buf->data = new_data; + buf->capacity = new_capacity; + } + + memcpy((void*)&buf->data[buf->size], data, data_size); + buf->size += data_size; + return true; +} + +uint8_t* buffer_get_data(Buffer* buf) { + return buf->data; +} + +size_t buffer_get_size(Buffer* buf) { + return buf->size; +} + +void buffer_close(Buffer* buf) { + buf->closed = true; +} + +void buffer_reset(Buffer* buf) { + for(size_t i = 0; i < MAX_BUFFER_SIZE; i++) { + buf->data[i] = 0; + } + buf->size = 0; + buf->closed = false; +} + +void buffer_free(Buffer* buf) { + free(buf->data); + free(buf); +} \ No newline at end of file diff --git a/applications/external/uhf_rfid/uhf_buffer.h b/applications/external/uhf_rfid/uhf_buffer.h new file mode 100644 index 000000000..045b0e3e4 --- /dev/null +++ b/applications/external/uhf_rfid/uhf_buffer.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include +#include + +#define MAX_BUFFER_SIZE 200 + +typedef struct Buffer { + uint8_t* data; + size_t size; + size_t capacity; + bool closed; +} Buffer; + +Buffer* buffer_alloc(size_t inital_capacity); +bool buffer_append_single(Buffer* buf, uint8_t value); +bool buffer_append(Buffer* buf, uint8_t* data, size_t size); +uint8_t* buffer_get_data(Buffer* buf); +size_t buffer_get_size(Buffer* buf); +void buffer_close(Buffer* buf); +void buffer_reset(Buffer* buf); +void buffer_free(Buffer* buf); \ No newline at end of file diff --git a/applications/external/uhf_rfid/uhf_device.c b/applications/external/uhf_rfid/uhf_device.c new file mode 100644 index 000000000..79ab50163 --- /dev/null +++ b/applications/external/uhf_rfid/uhf_device.c @@ -0,0 +1,350 @@ +#include "uhf_device.h" +#include +#include +#include + +#include + +#define TAG "UHFDevice" + +static const char* uhf_file_header = "Flipper UHF RFID device"; +static const uint32_t uhf_file_version = 1; +// static const uint8_t bank_data_start = 20; +// static const uint8_t bank_data_length = 16; + +UHFDevice* uhf_device_alloc() { + UHFDevice* uhf_device = malloc(sizeof(UHFDevice)); + uhf_device->storage = furi_record_open(RECORD_STORAGE); + uhf_device->dialogs = furi_record_open(RECORD_DIALOGS); + uhf_device->load_path = furi_string_alloc(); + return uhf_device; +} + +void uhf_device_set_name(UHFDevice* dev, const char* name) { + furi_assert(dev); + strlcpy(dev->dev_name, name, UHF_DEV_NAME_MAX_LEN); +} + +static bool uhf_device_save_file( + UHFDevice* dev, + const char* dev_name, + const char* folder, + const char* extension, + bool use_load_path) { + furi_assert(dev); + + UHFTag* uhf_tag = dev->uhf_tag_wrapper->uhf_tag; + bool saved = false; + FlipperFormat* file = flipper_format_file_alloc(dev->storage); + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + if(use_load_path && !furi_string_empty(dev->load_path)) { + // Get directory name + path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str); + // Make path to file to save + furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension); + } else { + // First remove uhf device file if it was saved + furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); + } + // Open file + if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; + + // Write header + if(!flipper_format_write_header_cstr(file, uhf_file_header, uhf_file_version)) break; + + // Reserved bank might be added + // todo : maybe + uint32_t temp_arr[1]; + uint8_t temp_arr2[2]; + // write pc + temp_arr2[0] = (uint8_t)(uhf_tag_get_epc_pc(uhf_tag) >> 8) & 0xFF; + temp_arr2[1] = (uint8_t)(uhf_tag_get_epc_pc(uhf_tag) & 0xFF); + if(!flipper_format_write_hex(file, UHF_EPC_PC_LABEL, temp_arr2, 2)) break; + // write crc + temp_arr2[0] = (uint8_t)(uhf_tag_get_epc_crc(uhf_tag) >> 8) & 0xFF; + temp_arr2[1] = (uint8_t)(uhf_tag_get_epc_crc(uhf_tag) & 0xFF); + if(!flipper_format_write_hex(file, UHF_EPC_CRC_LABEL, temp_arr2, 2)) break; + // write epc + temp_arr[0] = uhf_tag_get_epc_size(uhf_tag); + if(!flipper_format_write_uint32(file, UHF_EPC_BANK_LENGTH_LABEL, temp_arr, 1)) break; + if(!flipper_format_write_hex( + file, UHF_EPC_BANK_LABEL, uhf_tag_get_epc(uhf_tag), uhf_tag_get_epc_size(uhf_tag))) + break; + // write tid + temp_arr[0] = uhf_tag_get_tid_size(uhf_tag); + if(!flipper_format_write_uint32(file, UHF_TID_BANK_LENGTH_LABEL, temp_arr, 1)) break; + if(!flipper_format_write_hex( + file, UHF_TID_BANK_LABEL, uhf_tag_get_tid(uhf_tag), uhf_tag_get_tid_size(uhf_tag))) + break; + // write user + temp_arr[0] = uhf_tag_get_user_size(uhf_tag); + if(!flipper_format_write_uint32(file, UHF_USER_BANK_LENGTH_LABEL, temp_arr, 1)) break; + if(!flipper_format_write_hex( + file, + UHF_USER_BANK_LABEL, + uhf_tag_get_user(uhf_tag), + uhf_tag_get_user_size(uhf_tag))) + break; + saved = true; + } while(0); + + if(!saved) { + dialog_message_show_storage_error(dev->dialogs, "Can not save\nfile"); + } + furi_string_free(temp_str); + flipper_format_free(file); + return saved; +} + +bool uhf_device_save(UHFDevice* dev, const char* dev_name) { + return uhf_device_save_file( + dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, UHF_APP_EXTENSION, true); + + return false; +} +// uncomment + +static bool uhf_device_load_data(UHFDevice* dev, FuriString* path, bool show_dialog) { + bool parsed = false; + FlipperFormat* file = flipper_format_file_alloc(dev->storage); + // UHFResponseData* uhf_response_data = dev->dev_data; + FuriString* temp_str; + temp_str = furi_string_alloc(); + bool deprecated_version = false; + UHFTag* uhf_tag = uhf_tag_alloc(); + uhf_tag_reset(uhf_tag); + uint32_t temp_arr[1]; + if(dev->loading_cb) { + dev->loading_cb(dev->loading_cb_ctx, true); + } + + do { + if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break; + + // Read and verify file header + uint32_t version = 0; + if(!flipper_format_read_header(file, temp_str, &version)) break; + if(furi_string_cmp_str(temp_str, uhf_file_header) || (version != uhf_file_version)) { + deprecated_version = true; + break; + } + // read pc + uint8_t temp_arr2[2]; + if(!flipper_format_read_hex(file, UHF_EPC_PC_LABEL, temp_arr2, 2)) break; + uhf_tag_set_epc_pc(uhf_tag, (temp_arr2[0] << 8) + temp_arr2[1]); + // read crc + if(!flipper_format_read_hex(file, UHF_EPC_CRC_LABEL, temp_arr2, 2)) break; + uhf_tag_set_epc_crc(uhf_tag, (temp_arr2[0] << 8) + temp_arr2[1]); + // read epc + if(!flipper_format_read_uint32(file, UHF_EPC_BANK_LENGTH_LABEL, temp_arr, 1)) break; + uhf_tag_set_epc_size(uhf_tag, temp_arr[0]); + if(!flipper_format_read_hex( + file, UHF_EPC_BANK_LABEL, uhf_tag_get_epc(uhf_tag), uhf_tag_get_epc_size(uhf_tag))) + break; + + // read tid + if(!flipper_format_read_uint32(file, UHF_TID_BANK_LENGTH_LABEL, temp_arr, 1)) break; + uhf_tag_set_tid_size(uhf_tag, temp_arr[0]); + if(!flipper_format_read_hex( + file, UHF_TID_BANK_LABEL, uhf_tag_get_tid(uhf_tag), uhf_tag_get_tid_size(uhf_tag))) + break; + // read user + if(!flipper_format_read_uint32(file, UHF_USER_BANK_LENGTH_LABEL, temp_arr, 1)) break; + uhf_tag_set_user_size(uhf_tag, temp_arr[0]); + if(!flipper_format_read_hex( + file, + UHF_USER_BANK_LABEL, + uhf_tag_get_user(uhf_tag), + uhf_tag_get_user_size(uhf_tag))) + break; + + parsed = true; + } while(false); + + if(dev->loading_cb) { + dev->loading_cb(dev->loading_cb_ctx, false); + } + + if((!parsed) && (show_dialog)) { + if(deprecated_version) { + dialog_message_show_storage_error(dev->dialogs, "File format deprecated"); + } else { + dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile"); + } + } + uhf_tag_wrapper_set_tag(dev->uhf_tag_wrapper, uhf_tag); + furi_string_free(temp_str); + flipper_format_free(file); + return parsed; +} + +// void picopass_device_clear(UHFDevice* dev) { +// furi_assert(dev); + +// picopass_device_data_clear(&dev->dev_data); +// memset(&dev->dev_data, 0, sizeof(dev->dev_data)); +// dev->format = PicopassDeviceSaveFormatHF; +// furi_string_reset(dev->load_path); +// } + +void uhf_device_free(UHFDevice* uhf_dev) { + furi_assert(uhf_dev); + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_DIALOGS); + furi_string_free(uhf_dev->load_path); + free(uhf_dev); +} + +bool uhf_file_select(UHFDevice* dev) { + furi_assert(dev); + + FuriString* uhf_app_folder; + uhf_app_folder = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, UHF_APP_EXTENSION, &I_Nfc_10px); + browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; + + bool res = + dialog_file_browser_show(dev->dialogs, dev->load_path, uhf_app_folder, &browser_options); + + furi_string_free(uhf_app_folder); + if(res) { + FuriString* filename; + filename = furi_string_alloc(); + path_extract_filename(dev->load_path, filename, true); + strncpy(dev->dev_name, furi_string_get_cstr(filename), UHF_DEV_NAME_MAX_LEN); + res = uhf_device_load_data(dev, dev->load_path, true); + if(res) { + uhf_device_set_name(dev, dev->dev_name); + } + furi_string_free(filename); + } + + return res; +} + +// void uhf_device_data_clear(UHFDevice* dev_data) { +// for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { +// memset(dev_data->AA1[i].data, 0, sizeof(dev_data->AA1[i].data)); +// } +// dev_data->pacs.legacy = false; +// dev_data->pacs.se_enabled = false; +// dev_data->pacs.elite_kdf = false; +// dev_data->pacs.pin_length = 0; +// } + +bool uhf_device_delete(UHFDevice* dev, bool use_load_path) { + furi_assert(dev); + + bool deleted = false; + FuriString* file_path; + file_path = furi_string_alloc(); + + do { + // Delete original file + if(use_load_path && !furi_string_empty(dev->load_path)) { + furi_string_set(file_path, dev->load_path); + } else { + furi_string_printf(file_path, APP_DATA_PATH("%s%s"), dev->dev_name, UHF_APP_EXTENSION); + } + if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; + deleted = true; + } while(0); + + if(!deleted) { + dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); + } + + furi_string_free(file_path); + return deleted; +} + +void uhf_device_set_loading_callback(UHFDevice* dev, UHFLoadingCallback callback, void* context) { + furi_assert(dev); + + dev->loading_cb = callback; + dev->loading_cb_ctx = context; +} + +// ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) { +// uint8_t key[32] = {0}; +// memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey)); +// mbedtls_des3_context ctx; +// mbedtls_des3_init(&ctx); +// mbedtls_des3_set2key_dec(&ctx, key); +// mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data); +// mbedtls_des3_free(&ctx); +// return ERR_NONE; +// } + +// ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) { +// ReturnCode err; + +// pacs->biometrics = AA1[6].data[4]; +// pacs->pin_length = AA1[6].data[6] & 0x0F; +// pacs->encryption = AA1[6].data[7]; + +// if(pacs->encryption == PicopassDeviceEncryption3DES) { +// FURI_LOG_D(TAG, "3DES Encrypted"); +// err = picopass_device_decrypt(AA1[7].data, pacs->credential); +// if(err != ERR_NONE) { +// FURI_LOG_E(TAG, "decrypt error %d", err); +// return err; +// } + +// err = picopass_device_decrypt(AA1[8].data, pacs->pin0); +// if(err != ERR_NONE) { +// FURI_LOG_E(TAG, "decrypt error %d", err); +// return err; +// } + +// err = picopass_device_decrypt(AA1[9].data, pacs->pin1); +// if(err != ERR_NONE) { +// FURI_LOG_E(TAG, "decrypt error %d", err); +// return err; +// } +// } else if(pacs->encryption == PicopassDeviceEncryptionNone) { +// FURI_LOG_D(TAG, "No Encryption"); +// memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN); +// memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN); +// memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN); +// } else if(pacs->encryption == PicopassDeviceEncryptionDES) { +// FURI_LOG_D(TAG, "DES Encrypted"); +// } else { +// FURI_LOG_D(TAG, "Unknown encryption"); +// } + +// pacs->sio = (AA1[10].data[0] == 0x30); // rough check + +// return ERR_NONE; +// } + +// ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) { +// uint32_t* halves = (uint32_t*)data; +// if(halves[0] == 0) { +// uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); +// record->bitLength = 31 - leading0s; +// } else { +// uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); +// record->bitLength = 63 - leading0s; +// } +// FURI_LOG_D(TAG, "bitLength: %d", record->bitLength); + +// if(record->bitLength == 26) { +// uint8_t* v4 = data + 4; +// uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); + +// record->CardNumber = (bot >> 1) & 0xFFFF; +// record->FacilityCode = (bot >> 17) & 0xFF; +// FURI_LOG_D(TAG, "FC: %u CN: %u", record->FacilityCode, record->CardNumber); +// record->valid = true; +// } else { +// record->CardNumber = 0; +// record->FacilityCode = 0; +// record->valid = false; +// } +// return ERR_NONE; +// } diff --git a/applications/external/uhf_rfid/uhf_device.h b/applications/external/uhf_rfid/uhf_device.h new file mode 100644 index 000000000..8af0858b3 --- /dev/null +++ b/applications/external/uhf_rfid/uhf_device.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "uhf_tag.h" + +#define UHF_DEV_NAME_MAX_LEN 22 +#define UHF_EPC_BANK_LENGTH_LABEL "EPC_LENGTH" +#define UHF_TID_BANK_LENGTH_LABEL "TID_LENGTH" +#define UHF_USER_BANK_LENGTH_LABEL "USER_LENGTH" +#define UHF_EPC_PC_LABEL "PC" +#define UHF_EPC_CRC_LABEL "CRC" +#define UHF_RFU_BANK_LABEL "RFU" +#define UHF_EPC_BANK_LABEL "EPC" +#define UHF_TID_BANK_LABEL "TID" +#define UHF_USER_BANK_LABEL "USER" + +#define UHF_APP_EXTENSION ".uhf" + +typedef void (*UHFLoadingCallback)(void* context, bool state); + +typedef struct { + Storage* storage; + DialogsApp* dialogs; + UHFTagWrapper* uhf_tag_wrapper; + char dev_name[UHF_DEV_NAME_MAX_LEN + 1]; + FuriString* load_path; + UHFLoadingCallback loading_cb; + void* loading_cb_ctx; +} UHFDevice; + +UHFDevice* uhf_device_alloc(); + +void uhf_device_free(UHFDevice* uhf_dev); + +void uhf_device_set_name(UHFDevice* dev, const char* name); + +bool uhf_device_save(UHFDevice* dev, const char* dev_name); + +bool uhf_file_select(UHFDevice* dev); + +// void uhf_device_data_clear(PicopassDeviceData* dev_data); + +void uhf_device_clear(UHFDevice* dev); + +bool uhf_device_delete(UHFDevice* dev, bool use_load_path); + +void uhf_device_set_loading_callback(UHFDevice* dev, UHFLoadingCallback callback, void* context); + +// ReturnCode uhf_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs); +// ReturnCode uhf_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record); diff --git a/applications/external/uhf_rfid/uhf_module.c b/applications/external/uhf_rfid/uhf_module.c new file mode 100644 index 000000000..ee41d6092 --- /dev/null +++ b/applications/external/uhf_rfid/uhf_module.c @@ -0,0 +1,388 @@ +#include "uhf_module.h" +#include "uhf_module_cmd.h" + +#define DELAY_MS 100 +#define WAIT_TICK 8000 // max wait time in between each byte + +volatile uint16_t tick = 0; + +void rx_callback(UartIrqEvent event, uint8_t data, void* ctx) { + UNUSED(event); + Buffer* buffer = ctx; + if(buffer->closed) return; // buffer closed + buffer_append_single(buffer, data); // append data + if(data == FRAME_END) buffer_close(buffer); // end of frame + tick = WAIT_TICK; // reset tick +} + +static M100ResponseType setup_and_send_rx(M100Module* module, uint8_t* cmd, size_t cmd_length) { + buffer_reset(module->buf); + tick = WAIT_TICK; + furi_hal_uart_tx(FuriHalUartIdUSART1, cmd, cmd_length); + while(--tick) { + furi_delay_us(5); + } + buffer_close(module->buf); + // Validation Checks + uint8_t* data = buffer_get_data(module->buf); + size_t length = buffer_get_size(module->buf); + // check if size > 0 + if(!length) return M100EmptyResponse; + // check if data is valid + if(data[0] != FRAME_START || data[length - 1] != FRAME_END) return M100ValidationFail; + // check if checksum is correct + if(checksum(data + 1, length - 3) != data[length - 2]) return M100ChecksumFail; + return M100SuccessResponse; +} + +M100ModuleInfo* m100_module_info_alloc() { + M100ModuleInfo* module_info = (M100ModuleInfo*)malloc(sizeof(M100ModuleInfo)); + return module_info; +} + +void m100_module_info_free(M100ModuleInfo* module_info) { + free(module_info->hw_version); + free(module_info->sw_version); + free(module_info->manufacturer); + free(module_info); +} +M100Module* m100_module_alloc() { + M100Module* module = (M100Module*)malloc(sizeof(M100Module)); + module->info = m100_module_info_alloc(); + module->buf = buffer_alloc(MAX_BUFFER_SIZE); + module->baudrate = DEFAULT_BAUDRATE; + module->transmitting_power = DEFAULT_TRANSMITTING_POWER; + module->region = DEFAULT_WORKING_REGION; + furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, rx_callback, module->buf); + return module; +} + +void m100_module_free(M100Module* module) { + m100_module_info_free(module->info); + buffer_free(module->buf); + free(module); +} + +uint8_t checksum(const uint8_t* data, size_t length) { + // CheckSum8 Modulo 256 + // Sum of Bytes % 256 + uint64_t sum_val = 0x00; + for(size_t i = 0; i < length; i++) { + sum_val += data[i]; + } + return (uint8_t)(sum_val % 0x100); +} + +uint16_t crc16_genibus(const uint8_t* data, size_t length) { + uint16_t crc = 0xFFFF; // Initial value + uint16_t polynomial = 0x1021; // CRC-16/GENIBUS polynomial + + for(size_t i = 0; i < length; i++) { + crc ^= (data[i] << 8); // Move byte into MSB of 16bit CRC + for(int j = 0; j < 8; j++) { + if(crc & 0x8000) { + crc = (crc << 1) ^ polynomial; + } else { + crc <<= 1; + } + } + } + + return crc ^ 0xFFFF; // Post-inversion +} + +char* _m100_info_helper(M100Module* module, char** info) { + if(!buffer_get_size(module->buf)) return NULL; + uint8_t* data = buffer_get_data(module->buf); + uint16_t payload_len = data[3]; + payload_len = (payload_len << 8) + data[4]; + FuriString* temp_str = furi_string_alloc(); + for(int i = 0; i < payload_len; i++) { + furi_string_cat_printf(temp_str, "%c", data[6 + i]); + } + if(*info == NULL) { + *info = (char*)malloc(sizeof(char) * payload_len); + } else { + for(size_t i = 0; i < strlen(*info); i++) { + (*info)[i] = 0; + } + } + memcpy(*info, furi_string_get_cstr(temp_str), payload_len); + furi_string_free(temp_str); + return *info; +} + +char* m100_get_hardware_version(M100Module* module) { + setup_and_send_rx(module, (uint8_t*)&CMD_HW_VERSION.cmd[0], CMD_HW_VERSION.length); + return _m100_info_helper(module, &module->info->hw_version); +} +char* m100_get_software_version(M100Module* module) { + setup_and_send_rx(module, (uint8_t*)&CMD_SW_VERSION.cmd[0], CMD_SW_VERSION.length); + return _m100_info_helper(module, &module->info->sw_version); +} +char* m100_get_manufacturers(M100Module* module) { + setup_and_send_rx(module, (uint8_t*)&CMD_MANUFACTURERS.cmd[0], CMD_MANUFACTURERS.length); + return _m100_info_helper(module, &module->info->manufacturer); +} + +M100ResponseType m100_single_poll(M100Module* module, UHFTag* uhf_tag) { + M100ResponseType rp_type = + setup_and_send_rx(module, (uint8_t*)&CMD_SINGLE_POLLING.cmd[0], CMD_SINGLE_POLLING.length); + if(rp_type != M100SuccessResponse) return rp_type; + uint8_t* data = buffer_get_data(module->buf); + size_t length = buffer_get_size(module->buf); + uint16_t pc = data[6]; + uint16_t crc = 0; + // mask out epc length from protocol control + size_t epc_len = pc; + epc_len >>= 3; + epc_len *= 2; + // get protocol control + pc <<= 8; + pc += data[7]; + // get cyclic redundency check + crc = data[8 + epc_len]; + crc <<= 8; + crc += data[8 + epc_len + 1]; + // validate checksum + if(checksum(data + 1, length - 3) != data[length - 2]) return M100ValidationFail; + // validate crc + if(crc16_genibus(data + 6, epc_len + 2) != crc) return M100ValidationFail; + uhf_tag_set_epc_pc(uhf_tag, pc); + uhf_tag_set_epc_crc(uhf_tag, crc); + uhf_tag_set_epc(uhf_tag, data + 8, epc_len); + return M100SuccessResponse; +} + +M100ResponseType m100_set_select(M100Module* module, UHFTag* uhf_tag) { + // Set select + uint8_t cmd[MAX_BUFFER_SIZE]; + size_t cmd_length = CMD_SET_SELECT_PARAMETER.length; + size_t mask_length_bytes = uhf_tag->epc->size; + size_t mask_length_bits = mask_length_bytes * 8; + // payload len == sel param len + ptr len + mask len + epc len + size_t payload_len = 7 + mask_length_bytes; + memcpy(cmd, CMD_SET_SELECT_PARAMETER.cmd, cmd_length); + // set new length + cmd_length = 12 + mask_length_bytes + 2; + // set payload length + cmd[3] = (payload_len >> 8) & 0xFF; + cmd[4] = payload_len & 0xFF; + // set select param + cmd[5] = 0x01; // 0x00=rfu, 0x01=epc, 0x10=tid, 0x11=user + // set ptr + cmd[9] = 0x20; // epc data begins after 0x20 + // set mask length + cmd[10] = mask_length_bits; + // truncate + cmd[11] = false; + // set mask + memcpy((void*)&cmd[12], uhf_tag->epc->data, mask_length_bytes); + + // set checksum + cmd[cmd_length - 2] = checksum(cmd + 1, 11 + mask_length_bytes); + // end frame + cmd[cmd_length - 1] = FRAME_END; + + setup_and_send_rx(module, cmd, 12 + mask_length_bytes + 3); + + uint8_t* data = buffer_get_data(module->buf); + if(checksum(data + 1, 5) != data[6]) return M100ValidationFail; // error in rx + if(data[5] != 0x00) return M100ValidationFail; // error if not 0 + + return M100SuccessResponse; +} + +UHFTag* m100_get_select_param(M100Module* module) { + buffer_reset(module->buf); + furi_hal_uart_set_irq_cb(FuriHalUartIdLPUART1, rx_callback, module->buf); + furi_hal_uart_tx( + FuriHalUartIdUSART1, + (uint8_t*)&CMD_GET_SELECT_PARAMETER.cmd, + CMD_GET_SELECT_PARAMETER.length); + furi_delay_ms(DELAY_MS); + // UHFTag* uhf_tag = uhf_tag_alloc(); + // uint8_t* data = buffer_get_data(module->buf); + // size_t mask_length = + // uhf_tag_set_epc(uhf_tag, data + 12, ) + // TODO : implement + return NULL; +} + +M100ResponseType m100_read_label_data_storage( + M100Module* module, + UHFTag* uhf_tag, + BankType bank, + uint32_t access_pwd, + uint16_t word_count) { + /* + Will probably remove UHFTag as param and get it from get selected tag + */ + if(bank == EPCBank) return M100SuccessResponse; + uint8_t cmd[MAX_BUFFER_SIZE]; + size_t cmd_length = CMD_READ_LABEL_DATA_STORAGE_AREA.length; + memcpy(cmd, CMD_READ_LABEL_DATA_STORAGE_AREA.cmd, cmd_length); + // set access password + cmd[5] = (access_pwd >> 24) & 0xFF; + cmd[6] = (access_pwd >> 16) & 0xFF; + cmd[7] = (access_pwd >> 8) & 0xFF; + cmd[8] = access_pwd & 0xFF; + // set mem bank + cmd[9] = (uint8_t)bank; + // set word counter + cmd[12] = (word_count >> 8) & 0xFF; + cmd[13] = word_count & 0xFF; + // calc checksum + cmd[cmd_length - 2] = checksum(cmd + 1, cmd_length - 3); + + M100ResponseType rp_type = setup_and_send_rx(module, cmd, cmd_length); + if(rp_type != M100SuccessResponse) return rp_type; + + uint8_t* data = buffer_get_data(module->buf); + + uint8_t rtn_command = data[2]; + uint16_t payload_len = data[3]; + payload_len = (payload_len << 8) + data[4]; + + if(rtn_command == 0xFF) { + if(payload_len == 0x01) return M100NoTagResponse; + return M100MemoryOverrun; + } + + size_t ptr_offset = 5 /*<-ptr offset*/ + uhf_tag_get_epc_size(uhf_tag) + 3 /*<-pc + ul*/; + size_t bank_data_length = payload_len - (ptr_offset - 5 /*dont include the offset*/); + + if(bank == TIDBank) { + uhf_tag_set_tid(uhf_tag, data + ptr_offset, bank_data_length); + } else if(bank == UserBank) { + uhf_tag_set_user(uhf_tag, data + ptr_offset, bank_data_length); + } + + return M100SuccessResponse; +} + +M100ResponseType m100_write_label_data_storage( + M100Module* module, + UHFTag* saved_tag, + UHFTag* selected_tag, + BankType bank, + uint16_t source_address, + uint32_t access_pwd) { + uint8_t cmd[MAX_BUFFER_SIZE]; + size_t cmd_length = CMD_WRITE_LABEL_DATA_STORE.length; + memcpy(cmd, CMD_WRITE_LABEL_DATA_STORE.cmd, cmd_length); + uint16_t payload_len = 9; + uint16_t data_length = 0; + if(bank == ReservedBank) { + // access pwd len + kill pwd len + payload_len += 4; + data_length = 4; + } else if(bank == EPCBank) { + // epc len + pc len + payload_len += 4 + uhf_tag_get_epc_size(saved_tag); + data_length = 4 + uhf_tag_get_epc_size(saved_tag); + // set data + uint8_t tmp_arr[4]; + tmp_arr[0] = (uint8_t)((uhf_tag_get_epc_crc(selected_tag) >> 8) & 0xFF); + tmp_arr[1] = (uint8_t)(uhf_tag_get_epc_crc(selected_tag) & 0xFF); + tmp_arr[2] = (uint8_t)((uhf_tag_get_epc_pc(saved_tag) >> 8) & 0xFF); + tmp_arr[3] = (uint8_t)(uhf_tag_get_epc_pc(saved_tag) & 0xFF); + memcpy(cmd + 14, tmp_arr, 4); + memcpy(cmd + 18, uhf_tag_get_epc(saved_tag), uhf_tag_get_epc_size(saved_tag)); + } else if(bank == UserBank) { + payload_len += uhf_tag_get_user_size(saved_tag); + data_length = uhf_tag_get_user_size(saved_tag); + // set data + memcpy(cmd + 14, uhf_tag_get_user(saved_tag), uhf_tag_get_user_size(saved_tag)); + } + // set payload length + cmd[3] = (payload_len >> 8) & 0xFF; + cmd[4] = payload_len & 0xFF; + // set access password + cmd[5] = (access_pwd >> 24) & 0xFF; + cmd[6] = (access_pwd >> 16) & 0xFF; + cmd[7] = (access_pwd >> 8) & 0xFF; + cmd[8] = access_pwd & 0xFF; + // set membank + cmd[9] = (uint8_t)bank; + // set source address + cmd[10] = (source_address >> 8) & 0xFF; + cmd[11] = source_address & 0xFF; + // set data length + size_t data_length_words = data_length / 2; + cmd[12] = (data_length_words >> 8) & 0xFF; + cmd[13] = data_length_words & 0xFF; + // update cmd len + cmd_length = 7 + payload_len; + // calculate checksum + cmd[cmd_length - 2] = checksum(cmd + 1, cmd_length - 3); + cmd[cmd_length - 1] = FRAME_END; + // send cmd + // furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, rx_callback, module->buf); + // furi_hal_uart_tx(FuriHalUartIdUSART1, cmd, cmd_length); + // unsigned int delay = DELAY_MS / 2; + // unsigned int timeout = 15; + // while(!buffer_get_size(module->buf)) { + // furi_delay_ms(delay); + // if(!timeout--) break; + // } + setup_and_send_rx(module, cmd, cmd_length); + uint8_t* buff_data = buffer_get_data(module->buf); + size_t buff_length = buffer_get_size(module->buf); + if(buff_data[2] == 0xFF && buff_length == 8) + return M100NoTagResponse; + else if(buff_data[2] == 0xFF) + return M100ValidationFail; + return M100SuccessResponse; +} +void m100_set_baudrate(M100Module* module, uint32_t baudrate) { + size_t length = CMD_SET_COMMUNICATION_BAUD_RATE.length; + uint8_t cmd[length]; + memcpy(cmd, CMD_SET_COMMUNICATION_BAUD_RATE.cmd, length); + uint16_t br_mod = baudrate / 100; // module format + cmd[6] = 0xFF & br_mod; // pow LSB + cmd[5] = 0xFF & (br_mod >> 8); // pow MSB + cmd[length - 2] = checksum(cmd + 1, length - 3); + furi_hal_uart_tx(FuriHalUartIdUSART1, cmd, length); + furi_hal_uart_set_br(FuriHalUartIdUSART1, baudrate); + module->baudrate = baudrate; +} + +bool m100_set_working_region(M100Module* module, WorkingRegion region) { + size_t length = CMD_SET_WORK_AREA.length; + uint8_t cmd[length]; + memcpy(cmd, CMD_SET_WORK_AREA.cmd, length); + cmd[5] = (uint8_t)region; + cmd[length - 2] = checksum(cmd + 1, length - 3); + setup_and_send_rx(module, cmd, length); + module->region = region; + return true; +} + +bool m100_set_transmitting_power(M100Module* module, uint16_t power) { + size_t length = CMD_SET_TRANSMITTING_POWER.length; + uint8_t cmd[length]; + memcpy(cmd, CMD_SET_TRANSMITTING_POWER.cmd, length); + cmd[5] = (power >> 8) & 0xFF; + cmd[6] = power & 0xFF; + cmd[length - 2] = checksum(cmd + 1, length - 3); + setup_and_send_rx(module, cmd, length); + module->transmitting_power = power; + return true; +} + +bool m100_set_freq_hopping(M100Module* module, bool hopping) { + UNUSED(module); + UNUSED(hopping); + return true; +} + +bool m100_set_power(M100Module* module, uint8_t* power) { + UNUSED(module); + UNUSED(power); + return true; +} + +uint32_t m100_get_baudrate(M100Module* module) { + return module->baudrate; +} \ No newline at end of file diff --git a/applications/external/uhf_rfid/uhf_module.h b/applications/external/uhf_rfid/uhf_module.h new file mode 100644 index 000000000..2ac889f2f --- /dev/null +++ b/applications/external/uhf_rfid/uhf_module.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include +#include "uhf_tag.h" +#include "uhf_buffer.h" +#include "uhf_tag.h" +#include +#include "uhf_module_settings.h" + +#define FRAME_START 0xBB +#define FRAME_END 0x7E +#define DEFAULT_BAUDRATE BAUD_RATES[BAUD_RATES_COUNT - 1] +#define DEFAULT_TRANSMITTING_POWER POWER_DBM[POWER_DBM_COUNT - 1] +#define DEFAULT_WORKING_REGION WR_US + +typedef struct { + char* hw_version; + char* sw_version; + char* manufacturer; +} M100ModuleInfo; + +typedef enum { + M100SuccessResponse, + M100ValidationFail, + M100NoTagResponse, + M100MemoryOverrun, + M100EmptyResponse, + M100ChecksumFail +} M100ResponseType; + +typedef struct { + M100ModuleInfo* info; + uint32_t baudrate; + WorkingRegion region; + uint16_t region_frequency; + uint16_t transmitting_power; + bool freq_hopping; + Buffer* buf; +} M100Module; + +M100ModuleInfo* m100_module_info_alloc(); +void m100_module_info_free(M100ModuleInfo* module_info); + +M100Module* m100_module_alloc(); +void m100_module_free(M100Module* module); +uint16_t crc16_genibus(const uint8_t* data, size_t length); +uint8_t checksum(const uint8_t* data, size_t length); +uint8_t get_baudrate_count(); + +// Function prototypes +char* m100_get_hardware_version(M100Module* module); +char* m100_get_software_version(M100Module* module); +char* m100_get_manufacturers(M100Module* module); + +void m100_set_baudrate(M100Module* module, uint32_t baudrate); +bool m100_set_working_region(M100Module* module, WorkingRegion region); +bool m100_set_transmitting_power(M100Module* module, uint16_t power); +bool m100_set_freq_hopping(M100Module* module, bool hopping); +bool m100_set_power(M100Module* module, uint8_t* power); + +// gen2 cmds +M100ResponseType m100_single_poll(M100Module* module, UHFTag* uhf_tag); +M100ResponseType m100_set_select(M100Module* module, UHFTag* uhf_tag); +M100ResponseType m100_read_label_data_storage( + M100Module* module, + UHFTag* uhf_tag, + BankType bank, + uint32_t access_pwd, + uint16_t word_count); + +M100ResponseType m100_write_label_data_storage( + M100Module* module, + UHFTag* saved_tag, + UHFTag* selected_tag, + BankType bank, + uint16_t source_address, + uint32_t access_pwd); + +uint32_t m100_get_baudrate(M100Module* module); diff --git a/applications/external/uhf_rfid/uhf_module_cmd.h b/applications/external/uhf_rfid/uhf_module_cmd.h new file mode 100644 index 000000000..86605d8e9 --- /dev/null +++ b/applications/external/uhf_rfid/uhf_module_cmd.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include + +typedef struct { + const uint8_t* cmd; + size_t length; +} Command; + +// Define the command data arrays +static const uint8_t CMD_HW_VERSION_DATA[] = {0xBB, 0x00, 0x03, 0x00, 0x01, 0x00, 0x04, 0x7E}; +static const uint8_t CMD_SW_VERSION_DATA[] = {0xBB, 0x00, 0x03, 0x00, 0x01, 0x01, 0x05, 0x7E}; +static const uint8_t CMD_MANUFACTURERS_DATA[] = {0xBB, 0x00, 0x03, 0x00, 0x01, 0x02, 0x06, 0x7E}; +static const uint8_t CMD_SINGLE_POLLING_DATA[] = {0xBB, 0x00, 0x22, 0x00, 0x00, 0x22, 0x7E}; +static const uint8_t CMD_MULTIPLE_POLLING_DATA[] = + {0xBB, 0x00, 0x27, 0x00, 0x03, 0x22, 0x27, 0x10, 0x83, 0x7E}; +static const uint8_t CMD_STOP_MULTIPLE_POLLING_DATA[] = {0xBB, 0x00, 0x28, 0x00, 0x00, 0x28, 0x7E}; +static const uint8_t CMD_SET_SELECT_PARAMETER_DATA[] = {0xBB, 0x00, 0x0C, 0x00, 0x13, 0x01, 0x00, + 0x00, 0x00, 0x20, 0x60, 0x00, 0x30, 0x75, + 0x1F, 0xEB, 0x70, 0x5C, 0x59, 0x04, 0xE3, + 0xD5, 0x0D, 0x70, 0xAD, 0x7E}; +static const uint8_t CMD_GET_SELECT_PARAMETER_DATA[] = {0xBB, 0x00, 0x0B, 0x00, 0x00, 0x0B, 0x7E}; +static const uint8_t CMD_SET_SELECT_MODE_DATA[] = {0xBB, 0x00, 0x12, 0x00, 0x01, 0x01, 0x14, 0x7E}; +static const uint8_t CMD_READ_LABEL_DATA_STORAGE_AREA_DATA[] = + {0xBB, 0x00, 0x39, 0x00, 0x09, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x02, 0x45, 0x7E}; +static const uint8_t CMD_WRITE_LABEL_DATA_STORE_DATA[] = {0xBB, 0x00, 0x49, 0x00, 0x0D, 0x00, 0x00, + 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x02, + 0x12, 0x34, 0x56, 0x78, 0x6D, 0x7E}; +static const uint8_t CMD_LOCK_LABEL_DATA_STORE_DATA[] = + {0xBB, 0x00, 0x82, 0x00, 0x07, 0x00, 0x00, 0xFF, 0xFF, 0x02, 0x00, 0x80, 0x09, 0x7E}; +static const uint8_t CMD_INACTIVATE_KILL_TAG_DATA[] = + {0xBB, 0x00, 0x65, 0x00, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x67, 0x7E}; +static const uint8_t CMD_SET_COMMUNICATION_BAUD_RATE_DATA[] = + {0xBB, 0x00, 0x11, 0x00, 0x02, 0x00, 0xC0, 0xD3, 0x7E}; +static const uint8_t CMD_GET_QUERY_PARAMETERS_DATA[] = {0xBB, 0x00, 0x0D, 0x00, 0x00, 0x0D, 0x7E}; +static const uint8_t CMD_SET_QUERY_PARAMETER_DATA[] = + {0xBB, 0x00, 0x0E, 0x00, 0x02, 0x10, 0x20, 0x40, 0x7E}; +static const uint8_t CMD_SET_WORK_AREA_DATA[] = {0xBB, 0x00, 0x07, 0x00, 0x01, 0x01, 0x09, 0x7E}; +static const uint8_t CMD_SET_TRANSMITTING_POWER_DATA[] = + {0xBB, 0x00, 0xB6, 0x00, 0x02, 0x07, 0xD0, 0x8F, 0x7E}; + +// Define the Command structs +static const Command CMD_HW_VERSION = {CMD_HW_VERSION_DATA, sizeof(CMD_HW_VERSION_DATA)}; +static const Command CMD_SW_VERSION = {CMD_SW_VERSION_DATA, sizeof(CMD_SW_VERSION_DATA)}; +static const Command CMD_MANUFACTURERS = {CMD_MANUFACTURERS_DATA, sizeof(CMD_MANUFACTURERS_DATA)}; +static const Command CMD_SINGLE_POLLING = { + CMD_SINGLE_POLLING_DATA, + sizeof(CMD_SINGLE_POLLING_DATA)}; +static const Command CMD_MULTIPLE_POLLING = { + CMD_MULTIPLE_POLLING_DATA, + sizeof(CMD_MULTIPLE_POLLING_DATA)}; +static const Command CMD_STOP_MULTIPLE_POLLING = { + CMD_STOP_MULTIPLE_POLLING_DATA, + sizeof(CMD_STOP_MULTIPLE_POLLING_DATA)}; +static const Command CMD_SET_SELECT_PARAMETER = { + CMD_SET_SELECT_PARAMETER_DATA, + sizeof(CMD_SET_SELECT_PARAMETER_DATA)}; +static const Command CMD_GET_SELECT_PARAMETER = { + CMD_GET_SELECT_PARAMETER_DATA, + sizeof(CMD_GET_SELECT_PARAMETER_DATA)}; +static const Command CMD_SET_SELECT_MODE = { + CMD_SET_SELECT_MODE_DATA, + sizeof(CMD_SET_SELECT_MODE_DATA)}; +static const Command CMD_READ_LABEL_DATA_STORAGE_AREA = { + CMD_READ_LABEL_DATA_STORAGE_AREA_DATA, + sizeof(CMD_READ_LABEL_DATA_STORAGE_AREA_DATA)}; +static const Command CMD_WRITE_LABEL_DATA_STORE = { + CMD_WRITE_LABEL_DATA_STORE_DATA, + sizeof(CMD_WRITE_LABEL_DATA_STORE_DATA)}; +static const Command CMD_LOCK_LABEL_DATA_STORE = { + CMD_LOCK_LABEL_DATA_STORE_DATA, + sizeof(CMD_LOCK_LABEL_DATA_STORE_DATA)}; +static const Command CMD_INACTIVATE_KILL_TAG = { + CMD_INACTIVATE_KILL_TAG_DATA, + sizeof(CMD_INACTIVATE_KILL_TAG_DATA)}; +static const Command CMD_SET_COMMUNICATION_BAUD_RATE = { + CMD_SET_COMMUNICATION_BAUD_RATE_DATA, + sizeof(CMD_SET_COMMUNICATION_BAUD_RATE_DATA)}; +static const Command CMD_GET_QUERY_PARAMETERS = { + CMD_GET_QUERY_PARAMETERS_DATA, + sizeof(CMD_GET_QUERY_PARAMETERS_DATA)}; +static const Command CMD_SET_QUERY_PARAMETER = { + CMD_SET_QUERY_PARAMETER_DATA, + sizeof(CMD_SET_QUERY_PARAMETER_DATA)}; +static const Command CMD_SET_WORK_AREA = {CMD_SET_WORK_AREA_DATA, sizeof(CMD_SET_WORK_AREA_DATA)}; +static const Command CMD_SET_TRANSMITTING_POWER = { + CMD_SET_TRANSMITTING_POWER_DATA, + sizeof(CMD_SET_TRANSMITTING_POWER_DATA)}; diff --git a/applications/external/uhf_rfid/uhf_module_settings.h b/applications/external/uhf_rfid/uhf_module_settings.h new file mode 100644 index 000000000..d20a6f1a2 --- /dev/null +++ b/applications/external/uhf_rfid/uhf_module_settings.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// UHF module regions +typedef enum { + WR_CHINA_900 = 1, // Freq_CH-920.125M + WR_US, // Freq_CH-902.25M + WR_EU, // Freq_CH-865.1M + WR_CHINA_800, // Freq_CH-840.125M + WR_KOREA = 6 // Freq_CH-917.1M +} WorkingRegion; + +// UHF module baudrates +static const uint32_t BAUD_RATES[] = {9600, 19200, 115200}; +static const uint8_t BAUD_RATES_COUNT = sizeof(BAUD_RATES) / sizeof(BAUD_RATES[0]); +// RF Power Setting +static const uint8_t POWER_DBM[] = {12, 14, 17, 20}; // To be determined ... +static const uint8_t POWER_DBM_COUNT = sizeof(POWER_DBM) / sizeof(POWER_DBM[0]); +// UHF WorkingArea +static const char* WORKING_REGIONS_STR[] = {"CN1", "US", "EU", "CN2", "KR"}; +static const uint8_t __working_region_str = + sizeof(WORKING_REGIONS_STR) / sizeof(WORKING_REGIONS_STR[0]); +static const WorkingRegion WORKING_REGIONS[] = {WR_CHINA_900, WR_US, WR_EU, WR_CHINA_800, WR_KOREA}; +static const uint8_t WORKING_REGIONS_COUNT = sizeof(WORKING_REGIONS) / sizeof(WORKING_REGIONS[0]); +// UHF WorkingChannel +// static const string WORKING_CHANNELS_STR[] = {"China 900MHz", "US", "EU", "China 800MHz", "Korea"}; +// static const WorkingChannel WORKING_CHANNELS[] = {WC_CHINA_900, WC_US, WC_EU, WC_CHINA_800, WC_KOREA}; +// static const uint8_t WORKING_CHANNELS_COUNT = sizeof(WORKING_CHANNELS) / sizeof(WORKING_CHANNELS[0]); diff --git a/applications/external/uhf_rfid/uhf_tag.c b/applications/external/uhf_rfid/uhf_tag.c new file mode 100644 index 000000000..d1142c291 --- /dev/null +++ b/applications/external/uhf_rfid/uhf_tag.c @@ -0,0 +1,116 @@ +#include "uhf_tag.h" +#include +#include + +UHFTagWrapper* uhf_tag_wrapper_alloc() { + UHFTagWrapper* uhf_tag_wrapper = (UHFTagWrapper*)malloc(sizeof(UHFTagWrapper)); + uhf_tag_wrapper->uhf_tag = NULL; + return uhf_tag_wrapper; +} + +void uhf_tag_wrapper_set_tag(UHFTagWrapper* uhf_tag_wrapper, UHFTag* uhf_tag) { + if(uhf_tag_wrapper->uhf_tag != NULL) { + uhf_tag_free(uhf_tag_wrapper->uhf_tag); + } + uhf_tag_wrapper->uhf_tag = uhf_tag; +} + +void uhf_tag_wrapper_free(UHFTagWrapper* uhf_tag_wrapper) { + uhf_tag_free(uhf_tag_wrapper->uhf_tag); + free(uhf_tag_wrapper); +} + +UHFTag* uhf_tag_alloc() { + UHFTag* uhf_tag = (UHFTag*)malloc(sizeof(UHFTag)); + uhf_tag->reserved = (ReservedMemoryBank*)malloc(sizeof(ReservedMemoryBank)); + uhf_tag->epc = (EPCMemoryBank*)malloc(sizeof(EPCMemoryBank)); + uhf_tag->tid = (TIDMemoryBank*)malloc(sizeof(TIDMemoryBank)); + uhf_tag->user = (UserMemoryBank*)malloc(sizeof(UserMemoryBank)); + return uhf_tag; +} + +void uhf_tag_reset(UHFTag* uhf_tag) { + uhf_tag->epc->crc = 0; + uhf_tag->epc->pc = 0; + uhf_tag->epc->size = 0; + uhf_tag->tid->size = 0; + uhf_tag->user->size = 0; +} + +void uhf_tag_free(UHFTag* uhf_tag) { + if(uhf_tag == NULL) return; + free(uhf_tag->reserved); + free(uhf_tag->epc); + free(uhf_tag->tid); + free(uhf_tag->user); + free(uhf_tag); +} + +void uhf_tag_set_epc_pc(UHFTag* uhf_tag, uint16_t pc) { + uhf_tag->epc->pc = pc; +} + +void uhf_tag_set_epc_crc(UHFTag* uhf_tag, uint16_t crc) { + uhf_tag->epc->crc = crc; +} + +void uhf_tag_set_epc(UHFTag* uhf_tag, uint8_t* data_in, size_t size) { + memcpy(uhf_tag->epc->data, data_in, size); + uhf_tag->epc->size = size; +} + +void uhf_tag_set_epc_size(UHFTag* uhf_tag, size_t size) { + uhf_tag->epc->size = size; +} + +void uhf_tag_set_tid(UHFTag* uhf_tag, uint8_t* data_in, size_t size) { + memcpy(uhf_tag->tid->data, data_in, size); + uhf_tag->tid->size = size; +} + +void uhf_tag_set_tid_size(UHFTag* uhf_tag, size_t size) { + uhf_tag->tid->size = size; +} + +void uhf_tag_set_user(UHFTag* uhf_tag, uint8_t* data_in, size_t size) { + memcpy(uhf_tag->user->data, data_in, size); + uhf_tag->user->size = size; +} + +void uhf_tag_set_user_size(UHFTag* uhf_tag, size_t size) { + uhf_tag->user->size = size; +} + +// getters + +uint8_t* uhf_tag_get_epc(UHFTag* uhf_tag) { + return uhf_tag->epc->data; +} + +size_t uhf_tag_get_epc_size(UHFTag* uhf_tag) { + return uhf_tag->epc->size; +} + +uint16_t uhf_tag_get_epc_pc(UHFTag* uhf_tag) { + return uhf_tag->epc->pc; +} + +uint16_t uhf_tag_get_epc_crc(UHFTag* uhf_tag) { + return uhf_tag->epc->crc; +} + +uint8_t* uhf_tag_get_tid(UHFTag* uhf_tag) { + return uhf_tag->tid->data; +} + +size_t uhf_tag_get_tid_size(UHFTag* uhf_tag) { + return uhf_tag->tid->size; +} + +uint8_t* uhf_tag_get_user(UHFTag* uhf_tag) { + return uhf_tag->user->data; +} + +size_t uhf_tag_get_user_size(UHFTag* uhf_tag) { + return uhf_tag->user->size; +} diff --git a/applications/external/uhf_rfid/uhf_tag.h b/applications/external/uhf_rfid/uhf_tag.h new file mode 100644 index 000000000..bfc045ceb --- /dev/null +++ b/applications/external/uhf_rfid/uhf_tag.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +#define MAX_BANK_SIZE 256 +// storage enum +typedef enum { ReservedBank, EPCBank, TIDBank, UserBank } BankType; + +// Reserved Memory Bank +typedef struct { + uint8_t kill_password[2]; // 2 bytes (16 bits) for kill password + uint8_t access_password[2]; // 2 bytes (16 bits) for access password +} ReservedMemoryBank; + +// EPC Memory Bank +typedef struct { + size_t size; // Size of EPC memory data + uint8_t data[MAX_BANK_SIZE]; // 2 bytes for CRC16, 2 bytes for PC, and max 14 bytes for EPC + uint16_t pc; + uint16_t crc; +} EPCMemoryBank; + +// TID Memory Bank +typedef struct { + size_t size; // Size of TID memory data + uint8_t data[MAX_BANK_SIZE]; // 4 bytes for Class ID +} TIDMemoryBank; + +// User Memory Bank +typedef struct { + size_t size; // Size of user memory data + uint8_t data[MAX_BANK_SIZE]; // Assuming max 512 bits (64 bytes) for User Memory +} UserMemoryBank; + +// EPC Gen 2 Tag containing all memory banks +typedef struct { + ReservedMemoryBank* reserved; + EPCMemoryBank* epc; + TIDMemoryBank* tid; + UserMemoryBank* user; +} UHFTag; + +typedef struct UHFTagWrapper { + UHFTag* uhf_tag; +} UHFTagWrapper; + +UHFTagWrapper* uhf_tag_wrapper_alloc(); +void uhf_tag_wrapper_set_tag(UHFTagWrapper* uhf_tag_wrapper, UHFTag* uhf_tag); +void uhf_tag_wrapper_free(UHFTagWrapper* uhf_tag_wrapper); + +UHFTag* uhf_tag_alloc(); +void uhf_tag_reset(UHFTag* uhf_tag); +void uhf_tag_free(UHFTag* uhf_tag); + +void uhf_tag_set_kill_pwd(UHFTag* uhf_tag, uint8_t* data_in); +void uhf_tag_set_access_pwd(UHFTag* uhf_tag, uint8_t* data_in); +void uhf_tag_set_epc_pc(UHFTag* uhf_tag, uint16_t pc); +void uhf_tag_set_epc_crc(UHFTag* uhf_tag, uint16_t crc); +void uhf_tag_set_epc(UHFTag* uhf_tag, uint8_t* data_in, size_t size); +void uhf_tag_set_epc_size(UHFTag* uhf_tag, size_t size); +void uhf_tag_set_tid(UHFTag* uhf_tag, uint8_t* data_in, size_t size); +void uhf_tag_set_tid_size(UHFTag* uhf_tag, size_t size); +void uhf_tag_set_user(UHFTag* uhf_tag, uint8_t* data_in, size_t size); +void uhf_tag_set_user_size(UHFTag* uhf_tag, size_t size); + +uint8_t* uhf_tag_get_kill_pwd(UHFTag* uhf_tag); +uint8_t* uhf_tag_get_access_pwd(UHFTag* uhf_tag); +uint8_t* uhf_tag_get_epc(UHFTag* uhf_tag); +uint16_t uhf_tag_get_epc_pc(UHFTag* uhf_tag); +uint16_t uhf_tag_get_epc_crc(UHFTag* uhf_tag); +size_t uhf_tag_get_epc_size(UHFTag* uhf_tag); +uint8_t* uhf_tag_get_tid(UHFTag* uhf_tag); +size_t uhf_tag_get_tid_size(UHFTag* uhf_tag); +uint8_t* uhf_tag_get_user(UHFTag* uhf_tag); +size_t uhf_tag_get_user_size(UHFTag* uhf_tag); + +// debug +char* uhf_tag_get_cstr(UHFTag* uhf_tag); diff --git a/applications/external/uhf_rfid/uhf_worker.c b/applications/external/uhf_rfid/uhf_worker.c new file mode 100644 index 000000000..759114631 --- /dev/null +++ b/applications/external/uhf_rfid/uhf_worker.c @@ -0,0 +1,138 @@ +#include "uhf_worker.h" +#include "uhf_tag.h" + +// yrm100 module commands +UHFWorkerEvent verify_module_connected(UHFWorker* uhf_worker) { + char* hw_version = m100_get_hardware_version(uhf_worker->module); + char* sw_version = m100_get_software_version(uhf_worker->module); + char* manufacturer = m100_get_manufacturers(uhf_worker->module); + // verify all data exists + if(hw_version == NULL || sw_version == NULL || manufacturer == NULL) return UHFWorkerEventFail; + return UHFWorkerEventSuccess; +} + +UHFTag* send_polling_command(UHFWorker* uhf_worker) { + // read epc bank + UHFTag* uhf_tag = uhf_tag_alloc(); + while(true) { + M100ResponseType status = m100_single_poll(uhf_worker->module, uhf_tag); + if(uhf_worker->state == UHFWorkerStateStop) { + uhf_tag_free(uhf_tag); + return NULL; + } + if(status == M100SuccessResponse) break; + } + return uhf_tag; +} + +UHFWorkerEvent read_bank_till_max_length(UHFWorker* uhf_worker, UHFTag* uhf_tag, BankType bank) { + unsigned int word_low = 0, word_high = 64; + unsigned int word_size; + M100ResponseType status; + do { + if(uhf_worker->state == UHFWorkerStateStop) return UHFWorkerEventAborted; + if(word_low >= word_high) return UHFWorkerEventSuccess; + word_size = (word_low + word_high) / 2; + status = m100_read_label_data_storage(uhf_worker->module, uhf_tag, bank, 0, word_size); + if(status == M100SuccessResponse) { + word_low = word_size + 1; + } else if(status == M100MemoryOverrun) { + word_high = word_size - 1; + } + } while(true); + return UHFWorkerEventSuccess; +} + +UHFWorkerEvent read_single_card(UHFWorker* uhf_worker) { + UHFTag* uhf_tag = send_polling_command(uhf_worker); + if(uhf_tag == NULL) return UHFWorkerEventAborted; + uhf_tag_wrapper_set_tag(uhf_worker->uhf_tag_wrapper, uhf_tag); + // set select + if(m100_set_select(uhf_worker->module, uhf_tag) != M100SuccessResponse) + return UHFWorkerEventFail; + // read tid + UHFWorkerEvent event; + event = read_bank_till_max_length(uhf_worker, uhf_tag, TIDBank); + if(event != UHFWorkerEventSuccess) return event; + // read user + event = read_bank_till_max_length(uhf_worker, uhf_tag, UserBank); + if(event != UHFWorkerEventSuccess) return event; + return UHFWorkerEventSuccess; +} + +UHFWorkerEvent write_single_card(UHFWorker* uhf_worker) { + UHFTag* uhf_tag_des = send_polling_command(uhf_worker); + if(uhf_tag_des == NULL) return UHFWorkerEventAborted; + UHFTag* uhf_tag_from = uhf_worker->uhf_tag_wrapper->uhf_tag; + if(m100_set_select(uhf_worker->module, uhf_tag_des) != M100SuccessResponse) + return UHFWorkerEventFail; + do { + M100ResponseType rp_type = m100_write_label_data_storage( + uhf_worker->module, uhf_tag_from, uhf_tag_des, UserBank, 0, 0); + if(uhf_worker->state == UHFWorkerStateStop) return UHFWorkerEventAborted; + if(rp_type == M100SuccessResponse) break; + } while(true); + do { + M100ResponseType rp_type = m100_write_label_data_storage( + uhf_worker->module, uhf_tag_from, uhf_tag_des, EPCBank, 0, 0); + if(uhf_worker->state == UHFWorkerStateStop) return UHFWorkerEventAborted; + if(rp_type == M100SuccessResponse) break; + } while(true); + return UHFWorkerEventSuccess; +} + +int32_t uhf_worker_task(void* ctx) { + UHFWorker* uhf_worker = ctx; + if(uhf_worker->state == UHFWorkerStateVerify) { + UHFWorkerEvent event = verify_module_connected(uhf_worker); + uhf_worker->callback(event, uhf_worker->ctx); + } else if(uhf_worker->state == UHFWorkerStateDetectSingle) { + UHFWorkerEvent event = read_single_card(uhf_worker); + uhf_worker->callback(event, uhf_worker->ctx); + } else if(uhf_worker->state == UHFWorkerStateWriteSingle) { + UHFWorkerEvent event = write_single_card(uhf_worker); + uhf_worker->callback(event, uhf_worker->ctx); + } + return 0; +} + +UHFWorker* uhf_worker_alloc() { + UHFWorker* uhf_worker = (UHFWorker*)malloc(sizeof(UHFWorker)); + uhf_worker->thread = furi_thread_alloc_ex("UHFWorker", 8 * 1024, uhf_worker_task, uhf_worker); + uhf_worker->module = m100_module_alloc(); + uhf_worker->callback = NULL; + uhf_worker->ctx = NULL; + return uhf_worker; +} + +void uhf_worker_change_state(UHFWorker* worker, UHFWorkerState state) { + worker->state = state; +} + +void uhf_worker_start( + UHFWorker* uhf_worker, + UHFWorkerState state, + UHFWorkerCallback callback, + void* ctx) { + uhf_worker->state = state; + uhf_worker->callback = callback; + uhf_worker->ctx = ctx; + furi_thread_start(uhf_worker->thread); +} + +void uhf_worker_stop(UHFWorker* uhf_worker) { + furi_assert(uhf_worker); + furi_assert(uhf_worker->thread); + + if(furi_thread_get_state(uhf_worker->thread) != FuriThreadStateStopped) { + uhf_worker_change_state(uhf_worker, UHFWorkerStateStop); + furi_thread_join(uhf_worker->thread); + } +} + +void uhf_worker_free(UHFWorker* uhf_worker) { + furi_assert(uhf_worker); + furi_thread_free(uhf_worker->thread); + m100_module_free(uhf_worker->module); + free(uhf_worker); +} \ No newline at end of file diff --git a/applications/external/uhf_rfid/uhf_worker.h b/applications/external/uhf_rfid/uhf_worker.h new file mode 100644 index 000000000..b4f5aef1f --- /dev/null +++ b/applications/external/uhf_rfid/uhf_worker.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include "uhf_module.h" + +typedef enum { + // Init states + UHFWorkerStateNone, + UHFWorkerStateBroken, + UHFWorkerStateReady, + UHFWorkerStateVerify, + // Main worker states + UHFWorkerStateDetectSingle, + UHFWorkerStateWriteSingle, + UHFWorkerStateWriteKey, + // Transition + UHFWorkerStateStop, +} UHFWorkerState; + +typedef enum { + UHFWorkerEventSuccess, + UHFWorkerEventFail, + UHFWorkerEventNoTagDetected, + UHFWorkerEventAborted, + UHFWorkerEventCardDetected, +} UHFWorkerEvent; + +typedef void (*UHFWorkerCallback)(UHFWorkerEvent event, void* ctx); + +typedef struct UHFWorker { + FuriThread* thread; + M100Module* module; + UHFWorkerCallback callback; + UHFWorkerState state; + UHFTagWrapper* uhf_tag_wrapper; + void* ctx; +} UHFWorker; + +int32_t uhf_worker_task(void* ctx); +UHFWorker* uhf_worker_alloc(); +void uhf_worker_change_state(UHFWorker* worker, UHFWorkerState state); +void uhf_worker_start( + UHFWorker* uhf_worker, + UHFWorkerState state, + UHFWorkerCallback callback, + void* ctx); +void uhf_worker_stop(UHFWorker* uhf_worker); +void uhf_worker_free(UHFWorker* uhf_worker); \ No newline at end of file diff --git a/applications/main/archive/helpers/archive_files.c b/applications/main/archive/helpers/archive_files.c index df62030c8..e839fc772 100644 --- a/applications/main/archive/helpers/archive_files.c +++ b/applications/main/archive/helpers/archive_files.c @@ -135,11 +135,9 @@ FS_Error archive_copy_rename_file_or_dir( bool copy, bool find_name) { furi_assert(context); - const char* dst_cstr = furi_string_get_cstr(dst_path); + FURI_LOG_I( + TAG, "%s from %s to %s", copy ? "Copy" : "Move", src_path, furi_string_get_cstr(dst_path)); - FURI_LOG_I(TAG, "%s from %s to %s", copy ? "Copy" : "Move", src_path, dst_cstr); - - UNUSED(context); Storage* fs_api = furi_record_open(RECORD_STORAGE); FileInfo fileinfo; @@ -147,17 +145,17 @@ FS_Error archive_copy_rename_file_or_dir( FS_Error error = FSE_OK; - if(!path_contains_only_ascii(dst_cstr)) { + if(!path_contains_only_ascii(furi_string_get_cstr(dst_path))) { error = FSE_INVALID_NAME; - } else if(!copy && !strcmp(src_path, dst_cstr)) { + } else if(!copy && !strcmp(src_path, furi_string_get_cstr(dst_path))) { error = FSE_EXIST; } else { - if(find_name && storage_common_exists(fs_api, dst_cstr)) { + if(find_name && storage_common_exists(fs_api, furi_string_get_cstr(dst_path))) { FuriString* dir_path = furi_string_alloc(); FuriString* filename = furi_string_alloc(); FuriString* file_ext = furi_string_alloc(); - path_extract_dirname(dst_cstr, dir_path); + path_extract_dirname(furi_string_get_cstr(dst_path), dir_path); path_extract_filename(dst_path, filename, true); path_extract_ext_str(dst_path, file_ext); @@ -168,7 +166,8 @@ FS_Error archive_copy_rename_file_or_dir( furi_string_get_cstr(file_ext), dst_path, 255); - furi_string_cat_printf(dir_path, "/%s%s", dst_cstr, furi_string_get_cstr(file_ext)); + furi_string_cat_printf( + dir_path, "/%s%s", furi_string_get_cstr(dst_path), furi_string_get_cstr(file_ext)); furi_string_set(dst_path, dir_path); furi_string_free(dir_path); @@ -177,24 +176,31 @@ FS_Error archive_copy_rename_file_or_dir( } if(copy) { - error = storage_common_copy(fs_api, src_path, dst_cstr); + error = storage_common_copy(fs_api, src_path, furi_string_get_cstr(dst_path)); } else { - error = storage_common_rename(fs_api, src_path, dst_cstr); + error = storage_common_rename(fs_api, src_path, furi_string_get_cstr(dst_path)); } } furi_record_close(RECORD_STORAGE); if(!copy && archive_is_favorite("%s", src_path)) { - archive_favorites_rename(src_path, dst_cstr); + archive_favorites_rename(src_path, furi_string_get_cstr(dst_path)); } if(error == FSE_OK) { - FURI_LOG_I(TAG, "%s from %s to %s is DONE", copy ? "Copy" : "Move", src_path, dst_cstr); + FURI_LOG_I( + TAG, + "%s from %s to %s is DONE", + copy ? "Copy" : "Move", + src_path, + furi_string_get_cstr(dst_path)); } else { FURI_LOG_E( TAG, - "%s failed: %s, Code: %d", + "%s from %s to %s failed: %s, Code: %d", copy ? "Copy" : "Move", + src_path, + furi_string_get_cstr(dst_path), filesystem_api_error_get_desc(error), error); } diff --git a/applications/main/gpio/scenes/gpio_scene_start.c b/applications/main/gpio/scenes/gpio_scene_start.c index 28ced135a..3c35b8652 100644 --- a/applications/main/gpio/scenes/gpio_scene_start.c +++ b/applications/main/gpio/scenes/gpio_scene_start.c @@ -69,7 +69,9 @@ void gpio_scene_start_on_enter(void* context) { GpioOtgSettingsNum, gpio_scene_start_var_list_change_callback, app); - if(furi_hal_power_is_otg_enabled()) { + if(furi_hal_power_is_charging()) { + variable_item_set_locked(item, true, "Unplug USB!"); + } else if(furi_hal_power_is_otg_enabled()) { variable_item_set_current_value_index(item, GpioOtgOn); variable_item_set_current_value_text(item, gpio_otg_text[GpioOtgOn]); } else { diff --git a/applications/main/ibutton/scenes/ibutton_scene_info.c b/applications/main/ibutton/scenes/ibutton_scene_info.c index dcdee9557..50dc328a5 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_info.c +++ b/applications/main/ibutton/scenes/ibutton_scene_info.c @@ -8,21 +8,19 @@ void ibutton_scene_info_on_enter(void* context) { const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key); FuriString* tmp = furi_string_alloc(); + FuriString* keynumber = furi_string_alloc(); + + ibutton_protocols_render_brief_data(ibutton->protocols, key, keynumber); furi_string_printf( tmp, - "\e#%s\n[%s]\e#", + "\e#%s\n[%s]\e#\n%s", ibutton->key_name, - ibutton_protocols_get_name(ibutton->protocols, protocol_id)); + ibutton_protocols_get_name(ibutton->protocols, protocol_id), + furi_string_get_cstr(keynumber)); widget_add_text_box_element( - widget, 0, 2, 128, 40, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true); - - furi_string_reset(tmp); - ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp); - - widget_add_string_multiline_element( - widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + widget, 0, 2, 128, 64, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true); if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) & iButtonProtocolFeatureExtData) { @@ -32,6 +30,7 @@ void ibutton_scene_info_on_enter(void* context) { view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); furi_string_free(tmp); + furi_string_free(keynumber); } bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 4be0e2938..dc66e748b 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -204,6 +204,21 @@ static InfraredApp* infrared_alloc() { infrared->loading = loading_alloc(); infrared->progress = infrared_progress_view_alloc(); + infrared->last_settings = infrared_last_settings_alloc(); + infrared_last_settings_load(infrared->last_settings); + + if(infrared->last_settings->ext_5v) { + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + furi_delay_ms(10); + } + } + + if(infrared->last_settings->ext_out && !furi_hal_infrared_get_debug_out_status()) { + furi_hal_infrared_set_debug_out(true); + } + return infrared; } @@ -271,6 +286,14 @@ static void infrared_free(InfraredApp* infrared) { furi_string_free(infrared->file_path); furi_string_free(infrared->button_name); + if(infrared->last_settings->ext_5v) { + if(furi_hal_power_is_otg_enabled()) { + furi_hal_power_disable_otg(); + } + } + + infrared_last_settings_free(infrared->last_settings); + free(infrared); } diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 354e6ca65..544fde426 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -31,6 +31,7 @@ #include "infrared_remote.h" #include "infrared_brute_force.h" #include "infrared_custom_event.h" +#include "infrared_last_settings.h" #include "scenes/infrared_scene.h" #include "views/infrared_progress_view.h" @@ -38,6 +39,7 @@ #include "views/infrared_move_view.h" #include "rpc/rpc_app.h" +#include #define INFRARED_FILE_NAME_SIZE 100 #define INFRARED_TEXT_STORE_NUM 2 @@ -127,6 +129,7 @@ struct InfraredApp { /** Arbitrary text storage for various inputs. */ char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1]; InfraredAppState app_state; /**< Application state. */ + InfraredLastSettings* last_settings; /**< Last settings. */ void* rpc_ctx; /**< Pointer to the RPC context object. */ }; diff --git a/applications/main/infrared/infrared_last_settings.c b/applications/main/infrared/infrared_last_settings.c new file mode 100644 index 000000000..265c66ed4 --- /dev/null +++ b/applications/main/infrared/infrared_last_settings.c @@ -0,0 +1,88 @@ +#include "infrared_last_settings.h" + +#define TAG "InfraredLastSettings" + +#define INFRARED_LAST_SETTINGS_FILE_TYPE "Flipper Infrared Last Settings File" +#define INFRARED_LAST_SETTINGS_FILE_VERSION 1 +#define INFRARED_LAST_SETTINGS_PATH EXT_PATH("infrared/assets/last_infrared.settings") + +#define INFRARED_LAST_SETTINGS_FIELD_EXTPOWER "External5V" +#define INFRARED_LAST_SETTINGS_FIELD_EXTOUT "ExternalOut" + +InfraredLastSettings* infrared_last_settings_alloc(void) { + InfraredLastSettings* instance = malloc(sizeof(InfraredLastSettings)); + return instance; +} + +void infrared_last_settings_free(InfraredLastSettings* instance) { + furi_assert(instance); + free(instance); +} + +void infrared_last_settings_load(InfraredLastSettings* instance) { + furi_assert(instance); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + + bool temp_extpower = false; + bool temp_extout = false; + + if(FSE_OK == storage_sd_status(storage) && INFRARED_LAST_SETTINGS_PATH && + flipper_format_file_open_existing(fff_data_file, INFRARED_LAST_SETTINGS_PATH)) { + flipper_format_read_bool( + fff_data_file, INFRARED_LAST_SETTINGS_FIELD_EXTPOWER, (bool*)&temp_extpower, 1); + flipper_format_read_bool( + fff_data_file, INFRARED_LAST_SETTINGS_FIELD_EXTOUT, (bool*)&temp_extout, 1); + } else { + FURI_LOG_E(TAG, "Error open file %s", INFRARED_LAST_SETTINGS_PATH); + } + + instance->ext_5v = temp_extpower; + instance->ext_out = temp_extout; + + flipper_format_file_close(fff_data_file); + flipper_format_free(fff_data_file); + furi_record_close(RECORD_STORAGE); +} + +bool infrared_last_settings_save(InfraredLastSettings* instance) { + furi_assert(instance); + + bool saved = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + + do { + if(FSE_OK != storage_sd_status(storage)) { + break; + } + + // Open file + if(!flipper_format_file_open_always(file, INFRARED_LAST_SETTINGS_PATH)) break; + + // Write header + if(!flipper_format_write_header_cstr( + file, INFRARED_LAST_SETTINGS_FILE_TYPE, INFRARED_LAST_SETTINGS_FILE_VERSION)) + break; + + if(!flipper_format_insert_or_update_bool( + file, INFRARED_LAST_SETTINGS_FIELD_EXTPOWER, &instance->ext_5v, 1)) + break; + if(!flipper_format_insert_or_update_bool( + file, INFRARED_LAST_SETTINGS_FIELD_EXTOUT, &instance->ext_out, 1)) + break; + + saved = true; + } while(0); + + if(!saved) { + FURI_LOG_E(TAG, "Error save file %s", INFRARED_LAST_SETTINGS_PATH); + } + + flipper_format_file_close(file); + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); + + return saved; +} \ No newline at end of file diff --git a/applications/main/infrared/infrared_last_settings.h b/applications/main/infrared/infrared_last_settings.h new file mode 100644 index 000000000..45a15b2dd --- /dev/null +++ b/applications/main/infrared/infrared_last_settings.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + +typedef struct { + bool ext_5v; + bool ext_out; +} InfraredLastSettings; + +InfraredLastSettings* infrared_last_settings_alloc(void); +void infrared_last_settings_free(InfraredLastSettings* instance); +void infrared_last_settings_load(InfraredLastSettings* instance); +bool infrared_last_settings_save(InfraredLastSettings* instance); \ No newline at end of file diff --git a/applications/main/infrared/scenes/infrared_scene_debug_settings.c b/applications/main/infrared/scenes/infrared_scene_debug_settings.c index badfd172a..c1bed99cb 100644 --- a/applications/main/infrared/scenes/infrared_scene_debug_settings.c +++ b/applications/main/infrared/scenes/infrared_scene_debug_settings.c @@ -1,5 +1,4 @@ #include "../infrared_app_i.h" -#include uint8_t value_index_ir; @@ -12,14 +11,17 @@ const char* const infrared_debug_cfg_variables_text[] = { static void infrared_scene_debug_settings_changed(VariableItem* item) { InfraredApp* infrared = variable_item_get_context(item); value_index_ir = variable_item_get_current_value_index(item); - UNUSED(infrared); variable_item_set_current_value_text(item, infrared_debug_cfg_variables_text[value_index_ir]); furi_hal_infrared_set_debug_out(value_index_ir); + + infrared->last_settings->ext_out = value_index_ir == 1; + infrared_last_settings_save(infrared->last_settings); } static void infrared_scene_debug_settings_power_changed(VariableItem* item) { + InfraredApp* infrared = variable_item_get_context(item); bool value = variable_item_get_current_value_index(item); if(value) { for(int i = 0; i < 5 && !furi_hal_power_is_otg_enabled(); i++) { @@ -32,6 +34,9 @@ static void infrared_scene_debug_settings_power_changed(VariableItem* item) { } } variable_item_set_current_value_text(item, value ? "ON" : "OFF"); + + infrared->last_settings->ext_5v = value; + infrared_last_settings_save(infrared->last_settings); } static void infrared_debug_settings_start_var_list_enter_callback(void* context, uint32_t index) { @@ -64,7 +69,8 @@ void infrared_scene_debug_settings_on_enter(void* context) { 2, infrared_scene_debug_settings_power_changed, infrared); - bool enabled = furi_hal_power_is_otg_enabled(); + bool enabled = furi_hal_power_is_otg_enabled() || + furi_hal_power_is_charging(); // 5v is enabled via hardware if charging variable_item_set_current_value_index(item, enabled); variable_item_set_current_value_text(item, enabled ? "ON" : "OFF"); diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 166283149..8811063eb 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -55,6 +55,15 @@ App( sources=["plugins/supported_cards/troika.c"], ) +App( + appid="social_moscow_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="social_moscow_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/social_moscow.c"], +) + App( appid="plantain_parser", apptype=FlipperAppType.PLUGIN, @@ -73,6 +82,33 @@ App( sources=["plugins/supported_cards/two_cities.c"], ) +App( + appid="umarsh_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="umarsh_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/umarsh.c"], +) + +App( + appid="metromoney_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="metromoney_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/metromoney.c"], +) + +App( + appid="kazan_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="kazan_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/kazan.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index b3e629f4a..f9c849121 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -67,8 +67,14 @@ static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t even return false; } +static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) { + const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica); + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolFelica, data); + nfc_listener_start(instance->listener, NULL, NULL); +} + const NfcProtocolSupportBase nfc_protocol_support_felica = { - .features = NfcProtocolFeatureNone, + .features = NfcProtocolFeatureEmulateUid, .scene_info = { @@ -102,7 +108,7 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = { }, .scene_emulate = { - .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_enter = nfc_scene_emulate_on_enter_felica, .on_event = nfc_protocol_support_common_on_event_empty, }, }; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index bdd97e7f4..9b95fba35 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -586,6 +586,7 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating"); furi_string_set( temp_str, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); + furi_string_cat_printf(temp_str, "\n%s", furi_string_get_cstr(instance->file_name)); } widget_add_text_box_element( diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c new file mode 100644 index 000000000..68ef6c16f --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -0,0 +1,287 @@ +/* + * Parser for Kazan transport card (Kazan, Russia). + * + * Copyright 2023 Leptoptilos + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "core/log.h" +#include "nfc_supported_card_plugin.h" + +#include "protocols/mf_classic/mf_classic.h" +#include + +#include +#include +#include +#include +#include +#include +#include "md5.h" + +#define TAG "Kazan" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static const MfClassicKeyPair kazan_1k_keys[] = { + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xE954024EE754, .b = 0x0CD464CDC100}, + {.a = 0xBC305FE2DA65, .b = 0xCF0EC6ACF2F9}, + {.a = 0xF7A545095C49, .b = 0x6862FD600F78}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, +}; + +enum SubscriptionType { + SUBSCRIPTION_TYPE_UNKNOWN, + SUBSCRIPTION_TYPE_PURSE, + SUBSCRIPTION_TYPE_ABONNEMENT, +}; + +enum SubscriptionType get_subscription_type(uint8_t value) { + switch(value) { + case 0: + case 0x60: + case 0x67: + case 0x0F: + return SUBSCRIPTION_TYPE_ABONNEMENT; + case 0x53: + return SUBSCRIPTION_TYPE_PURSE; + default: + return SUBSCRIPTION_TYPE_UNKNOWN; + } +} + +static bool kazan_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t ticket_sector_number = 8; + const uint8_t ticket_block_number = + mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1; + FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number); + + MfClassicKey key = {0}; + nfc_util_num2bytes(kazan_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = mf_classic_poller_sync_auth( + nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool kazan_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + if(type != MfClassicType1k) break; + + MfClassicDeviceKeys keys = { + .key_a_mask = 0, + .key_b_mask = 0, + }; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(kazan_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(kazan_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + const uint8_t ticket_sector_number = 8; + const uint8_t balance_sector_number = 9; + + // Verify keys + MfClassicKeyPair keys = {}; + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number); + + keys.a = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + keys.b = nfc_util_bytes2num(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data)); + + if((keys.a != 0xE954024EE754) && (keys.b != 0x0CD464CDC100)) break; + + // Parse data + uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(ticket_sector_number); + + const uint8_t* block_start_ptr = &data->block[start_block_num].data[6]; + + enum SubscriptionType subscription_type = get_subscription_type(block_start_ptr[0]); + + FuriHalRtcDateTime valid_from; + valid_from.year = 2000 + block_start_ptr[1]; + valid_from.month = block_start_ptr[2]; + valid_from.day = block_start_ptr[3]; + + FuriHalRtcDateTime valid_to; + valid_to.year = 2000 + block_start_ptr[4]; + valid_to.month = block_start_ptr[5]; + valid_to.day = block_start_ptr[6]; + + const uint8_t last_trip_block_number = 2; + block_start_ptr = &data->block[start_block_num + last_trip_block_number].data[1]; + + FuriHalRtcDateTime last_trip; + last_trip.year = 2000 + block_start_ptr[0]; + last_trip.month = block_start_ptr[1]; + last_trip.day = block_start_ptr[2]; + last_trip.hour = block_start_ptr[3]; + last_trip.minute = block_start_ptr[4]; + bool is_last_trip_valid = (block_start_ptr[0] | block_start_ptr[1] | block_start_ptr[0]) && + (last_trip.day < 32 && last_trip.month < 12 && + last_trip.hour < 24 && last_trip.minute < 60); + + start_block_num = mf_classic_get_first_block_num_of_sector(balance_sector_number); + block_start_ptr = &data->block[start_block_num].data[0]; + + const uint32_t trip_counter = (block_start_ptr[3] << 24) | (block_start_ptr[2] << 16) | + (block_start_ptr[1] << 8) | (block_start_ptr[0]); + + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + const uint32_t card_number = (uid[3] << 24) | (uid[2] << 16) | (uid[1] << 8) | (uid[0]); + + furi_string_cat_printf( + parsed_data, "\e#Kazan transport card\nCard number: %lu\n", card_number); + + if(subscription_type == SUBSCRIPTION_TYPE_PURSE) { + furi_string_cat_printf( + parsed_data, + "Type: purse\nBalance: %lu RUR\nBalance valid:\nfrom: %02u.%02u.%u\nto: %02u.%02u.%u", + trip_counter, + valid_from.day, + valid_from.month, + valid_from.year, + valid_to.day, + valid_to.month, + valid_to.year); + } + + if(subscription_type == SUBSCRIPTION_TYPE_ABONNEMENT) { + furi_string_cat_printf( + parsed_data, + "Type: abonnement\nTrips left: %lu\nCard valid:\nfrom: %02u.%02u.%u\nto: %02u.%02u.%u", + trip_counter, + valid_from.day, + valid_from.month, + valid_from.year, + valid_to.day, + valid_to.month, + valid_to.year); + } + + if(subscription_type == SUBSCRIPTION_TYPE_UNKNOWN) { + furi_string_cat_printf( + parsed_data, + "Type: unknown\nBalance: %lu RUR\nValid from: %02u.%02u.%u\nValid to: %02u.%02u.%u", + trip_counter, + valid_from.day, + valid_from.month, + valid_from.year, + valid_to.day, + valid_to.month, + valid_to.year); + } + + if(is_last_trip_valid) { + furi_string_cat_printf( + parsed_data, + "\nLast trip: %02u.%02u.%u at %02u:%02u", + last_trip.day, + last_trip.month, + last_trip.year, + last_trip.hour, + last_trip.minute); + } + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin kazan_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = kazan_verify, + .read = kazan_read, + .parse = kazan_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor kazan_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &kazan_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* kazan_plugin_ep() { + return &kazan_plugin_descriptor; +} \ No newline at end of file diff --git a/applications/main/nfc/plugins/supported_cards/metromoney.c b/applications/main/nfc/plugins/supported_cards/metromoney.c new file mode 100644 index 000000000..263bbc44e --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/metromoney.c @@ -0,0 +1,191 @@ +/* + * Parser for Metromoney card (Georgia). + * + * Copyright 2023 Leptoptilos + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "nfc_supported_card_plugin.h" + +#include "protocols/mf_classic/mf_classic.h" +#include + +#include +#include +#include +#include + +#define TAG "Metromoney" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static const MfClassicKeyPair metromoney_1k_keys[] = { + {.a = 0x2803BCB0C7E1, .b = 0x4FA9EB49F75E}, + {.a = 0x9C616585E26D, .b = 0xD1C71E590D16}, + {.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C}, + {.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C}, + {.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C}, + {.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0x112233445566, .b = 0x361A62F35BC9}, + {.a = 0x112233445566, .b = 0x361A62F35BC9}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, +}; + +static bool metromoney_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t ticket_sector_number = 1; + const uint8_t ticket_block_number = + mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1; + FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number); + + MfClassicKey key = {0}; + nfc_util_num2bytes( + metromoney_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = mf_classic_poller_sync_auth( + nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool metromoney_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + if(type != MfClassicType1k) break; + + MfClassicDeviceKeys keys = { + .key_a_mask = 0, + .key_b_mask = 0, + }; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(metromoney_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(metromoney_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool metromoney_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify key + const uint8_t ticket_sector_number = 1; + const uint8_t ticket_block_number = 1; + + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number); + + const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + if(key != metromoney_1k_keys[ticket_sector_number].a) break; + + // Parse data + const uint8_t start_block_num = + mf_classic_get_first_block_num_of_sector(ticket_sector_number); + + const uint8_t* block_start_ptr = + &data->block[start_block_num + ticket_block_number].data[0]; + + uint32_t balance = (block_start_ptr[3] << 24) | (block_start_ptr[2] << 16) | + (block_start_ptr[1] << 8) | (block_start_ptr[0]); + + uint32_t balance_lari = balance / 100; + uint8_t balance_tetri = balance % 100; + + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + uint32_t card_number = (uid[3] << 24) | (uid[2] << 16) | (uid[1] << 8) | (uid[0]); + + furi_string_printf( + parsed_data, + "\e#Metromoney\nCard number: %lu\nBalance: %lu.%02u GEL", + card_number, + balance_lari, + balance_tetri); + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin metromoney_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = metromoney_verify, + .read = metromoney_read, + .parse = metromoney_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor metromoney_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &metromoney_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* metromoney_plugin_ep() { + return &metromoney_plugin_descriptor; +} \ No newline at end of file diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c new file mode 100644 index 000000000..ddd3c2db5 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -0,0 +1,1629 @@ +#include "nfc_supported_card_plugin.h" +#include + +#include + +#include +#include +#include +#include +#include +#include + +#define TAG "Social_Moscow" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + const MfClassicKeyPair* keys; + uint32_t data_sector; +} SocialMoscowCardConfig; + +static const MfClassicKeyPair social_moscow_1k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, + {.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, + {.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, + {.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, + {.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, + {.a = 0x40ead80721ce, .b = 0x100533b89331}, + {.a = 0x0db5e6523f7c, .b = 0x653a87594079}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}}; + +static const MfClassicKeyPair social_moscow_4k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, {.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, {.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, + {.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, {.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, + {.a = 0x40ead80721ce, .b = 0x100533b89331}, {.a = 0x0db5e6523f7c, .b = 0x653a87594079}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, +}; + +#define TOPBIT(X) (1 << ((X)-1)) + +typedef enum { + BitLibParityEven, + BitLibParityOdd, + BitLibParityAlways0, + BitLibParityAlways1, +} BitLibParity; + +typedef struct { + const char mark; + const size_t start; + const size_t length; +} BitLibRegion; + +void bit_lib_push_bit(uint8_t* data, size_t data_size, bool bit) { + size_t last_index = data_size - 1; + + for(size_t i = 0; i < last_index; ++i) { + data[i] = (data[i] << 1) | ((data[i + 1] >> 7) & 1); + } + data[last_index] = (data[last_index] << 1) | bit; +} + +void bit_lib_set_bit(uint8_t* data, size_t position, bool bit) { + if(bit) { + data[position / 8] |= 1UL << (7 - (position % 8)); + } else { + data[position / 8] &= ~(1UL << (7 - (position % 8))); + } +} + +void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t length) { + furi_check(length <= 8); + furi_check(length > 0); + + for(uint8_t i = 0; i < length; ++i) { + uint8_t shift = (length - 1) - i; + bit_lib_set_bit(data, position + i, (byte >> shift) & 1); //-V610 + } +} + +bool bit_lib_get_bit(const uint8_t* data, size_t position) { + return (data[position / 8] >> (7 - (position % 8))) & 1; +} + +uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length) { + uint8_t shift = position % 8; + if(shift == 0) { + return data[position / 8] >> (8 - length); + } else { + // TODO fix read out of bounds + uint8_t value = (data[position / 8] << (shift)); + value |= data[position / 8 + 1] >> (8 - shift); + value = value >> (8 - length); + return value; + } +} + +uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t length) { + uint16_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } + return value; +} + +uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length) { + uint32_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else if(length <= 16) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } else if(length <= 24) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, length - 16); + } else { + value = (uint32_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint32_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint32_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= bit_lib_get_bits(data, position + 24, length - 24); + } + + return value; +} + +uint64_t bit_lib_get_bits_64(const uint8_t* data, size_t position, uint8_t length) { + uint64_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else if(length <= 16) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } else if(length <= 24) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, length - 16); + } else if(length <= 32) { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= bit_lib_get_bits(data, position + 24, length - 24); + } else { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= (uint64_t)bit_lib_get_bits(data, position + 24, 8) << (length - 32); + value |= (uint64_t)bit_lib_get_bits(data, position + 32, 8) << (length - 40); + value |= (uint64_t)bit_lib_get_bits(data, position + 40, 8) << (length - 48); + value |= (uint64_t)bit_lib_get_bits(data, position + 48, 8) << (length - 56); + value |= (uint64_t)bit_lib_get_bits(data, position + 56, 8) << (length - 64); + value |= bit_lib_get_bits(data, position + 64, length - 64); + } + + return value; +} + +bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity) { +#if !defined __GNUC__ +#error Please, implement parity test for non-GCC compilers +#else + switch(parity) { + case BitLibParityEven: + return __builtin_parity(bits); + case BitLibParityOdd: + return !__builtin_parity(bits); + default: + furi_crash("Unknown parity"); + } +#endif +} + +bool bit_lib_test_parity( + const uint8_t* bits, + size_t position, + uint8_t length, + BitLibParity parity, + uint8_t parity_length) { + uint32_t parity_block; + bool result = true; + const size_t parity_blocks_count = length / parity_length; + + for(size_t i = 0; i < parity_blocks_count; ++i) { + switch(parity) { + case BitLibParityEven: + case BitLibParityOdd: + parity_block = bit_lib_get_bits_32(bits, position + i * parity_length, parity_length); + if(!bit_lib_test_parity_32(parity_block, parity)) { + result = false; + } + break; + case BitLibParityAlways0: + if(bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { + result = false; + } + break; + case BitLibParityAlways1: + if(!bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { + result = false; + } + break; + } + + if(!result) break; + } + return result; +} + +size_t bit_lib_add_parity( + const uint8_t* data, + size_t position, + uint8_t* dest, + size_t dest_position, + uint8_t source_length, + uint8_t parity_length, + BitLibParity parity) { + uint32_t parity_word = 0; + size_t j = 0, bit_count = 0; + for(int word = 0; word < source_length; word += parity_length - 1) { + for(int bit = 0; bit < parity_length - 1; bit++) { + parity_word = (parity_word << 1) | bit_lib_get_bit(data, position + word + bit); + bit_lib_set_bit( + dest, dest_position + j++, bit_lib_get_bit(data, position + word + bit)); + } + // if parity fails then return 0 + switch(parity) { + case BitLibParityAlways0: + bit_lib_set_bit(dest, dest_position + j++, 0); + break; // marker bit which should be a 0 + case BitLibParityAlways1: + bit_lib_set_bit(dest, dest_position + j++, 1); + break; // marker bit which should be a 1 + default: + bit_lib_set_bit( + dest, + dest_position + j++, + (bit_lib_test_parity_32(parity_word, BitLibParityOdd) ^ parity) ^ 1); + break; + } + bit_count += parity_length; + parity_word = 0; + } + // if we got here then all the parities passed + // return bit count + return bit_count; +} + +size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n) { + size_t counter = 0; + size_t result_counter = 0; + uint8_t bit_buffer = 0; + uint8_t bit_counter = 0; + + while(counter < length) { + if((counter + 1) % n != 0) { + bit_buffer = (bit_buffer << 1) | bit_lib_get_bit(data, position + counter); + bit_counter++; + } + + if(bit_counter == 8) { + bit_lib_set_bits(data, position + result_counter, bit_buffer, 8); + bit_counter = 0; + bit_buffer = 0; + result_counter += 8; + } + counter++; + } + + if(bit_counter != 0) { + bit_lib_set_bits(data, position + result_counter, bit_buffer, bit_counter); + result_counter += bit_counter; + } + return result_counter; +} + +void bit_lib_copy_bits( + uint8_t* data, + size_t position, + size_t length, + const uint8_t* source, + size_t source_position) { + for(size_t i = 0; i < length; ++i) { + bit_lib_set_bit(data, position + i, bit_lib_get_bit(source, source_position + i)); + } +} + +void bit_lib_reverse_bits(uint8_t* data, size_t position, uint8_t length) { + size_t i = 0; + size_t j = length - 1; + + while(i < j) { + bool tmp = bit_lib_get_bit(data, position + i); + bit_lib_set_bit(data, position + i, bit_lib_get_bit(data, position + j)); + bit_lib_set_bit(data, position + j, tmp); + i++; + j--; + } +} + +uint8_t bit_lib_get_bit_count(uint32_t data) { +#if defined __GNUC__ + return __builtin_popcountl(data); +#else +#error Please, implement popcount for non-GCC compilers +#endif +} + +void bit_lib_print_bits(const uint8_t* data, size_t length) { + for(size_t i = 0; i < length; ++i) { + printf("%u", bit_lib_get_bit(data, i)); + } +} + +void bit_lib_print_regions( + const BitLibRegion* regions, + size_t region_count, + const uint8_t* data, + size_t length) { + // print data + bit_lib_print_bits(data, length); + printf("\r\n"); + + // print regions + for(size_t c = 0; c < length; ++c) { + bool print = false; + + for(size_t i = 0; i < region_count; i++) { + if(regions[i].start <= c && c < regions[i].start + regions[i].length) { + print = true; + printf("%c", regions[i].mark); + break; + } + } + + if(!print) { + printf(" "); + } + } + printf("\r\n"); + + // print regions data + for(size_t c = 0; c < length; ++c) { + bool print = false; + + for(size_t i = 0; i < region_count; i++) { + if(regions[i].start <= c && c < regions[i].start + regions[i].length) { + print = true; + printf("%u", bit_lib_get_bit(data, c)); + break; + } + } + + if(!print) { + printf(" "); + } + } + printf("\r\n"); +} + +uint16_t bit_lib_reverse_16_fast(uint16_t data) { + uint16_t result = 0; + result |= (data & 0x8000) >> 15; + result |= (data & 0x4000) >> 13; + result |= (data & 0x2000) >> 11; + result |= (data & 0x1000) >> 9; + result |= (data & 0x0800) >> 7; + result |= (data & 0x0400) >> 5; + result |= (data & 0x0200) >> 3; + result |= (data & 0x0100) >> 1; + result |= (data & 0x0080) << 1; + result |= (data & 0x0040) << 3; + result |= (data & 0x0020) << 5; + result |= (data & 0x0010) << 7; + result |= (data & 0x0008) << 9; + result |= (data & 0x0004) << 11; + result |= (data & 0x0002) << 13; + result |= (data & 0x0001) << 15; + return result; +} + +uint8_t bit_lib_reverse_8_fast(uint8_t byte) { + byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4; + byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2; + byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1; + return byte; +} + +uint16_t bit_lib_crc8( + uint8_t const* data, + size_t data_size, + uint8_t polynom, + uint8_t init, + bool ref_in, + bool ref_out, + uint8_t xor_out) { + uint8_t crc = init; + + for(size_t i = 0; i < data_size; ++i) { + uint8_t byte = data[i]; + if(ref_in) bit_lib_reverse_bits(&byte, 0, 8); + crc ^= byte; + + for(size_t j = 8; j > 0; --j) { + if(crc & TOPBIT(8)) { + crc = (crc << 1) ^ polynom; + } else { + crc = (crc << 1); + } + } + } + + if(ref_out) bit_lib_reverse_bits(&crc, 0, 8); + crc ^= xor_out; + + return crc; +} + +uint16_t bit_lib_crc16( + uint8_t const* data, + size_t data_size, + uint16_t polynom, + uint16_t init, + bool ref_in, + bool ref_out, + uint16_t xor_out) { + uint16_t crc = init; + + for(size_t i = 0; i < data_size; ++i) { + uint8_t byte = data[i]; + if(ref_in) byte = bit_lib_reverse_16_fast(byte) >> 8; + + for(size_t j = 0; j < 8; ++j) { + bool c15 = (crc >> 15 & 1); + bool bit = (byte >> (7 - j) & 1); + crc <<= 1; + if(c15 ^ bit) crc ^= polynom; + } + } + + if(ref_out) crc = bit_lib_reverse_16_fast(crc); + crc ^= xor_out; + + return crc; +} + +#define FURI_HAL_RTC_SECONDS_PER_MINUTE 60 +#define FURI_HAL_RTC_SECONDS_PER_HOUR (FURI_HAL_RTC_SECONDS_PER_MINUTE * 60) +#define FURI_HAL_RTC_SECONDS_PER_DAY (FURI_HAL_RTC_SECONDS_PER_HOUR * 24) +#define FURI_HAL_RTC_EPOCH_START_YEAR 1970 +#define FURI_HAL_RTC_IS_LEAP_YEAR(year) \ + ((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0)) + +void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { + uint32_t days = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; + uint32_t seconds_in_day = timestamp % FURI_HAL_RTC_SECONDS_PER_DAY; + + datetime->year = FURI_HAL_RTC_EPOCH_START_YEAR; + + while(days >= furi_hal_rtc_get_days_per_year(datetime->year)) { + days -= furi_hal_rtc_get_days_per_year(datetime->year); + (datetime->year)++; + } + + datetime->month = 1; + while(days >= furi_hal_rtc_get_days_per_month( + FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month)) { + days -= furi_hal_rtc_get_days_per_month( + FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month); + (datetime->month)++; + } + + datetime->day = days + 1; + datetime->hour = seconds_in_day / FURI_HAL_RTC_SECONDS_PER_HOUR; + datetime->minute = + (seconds_in_day % FURI_HAL_RTC_SECONDS_PER_HOUR) / FURI_HAL_RTC_SECONDS_PER_MINUTE; + datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE; +} + +void from_days_to_datetime(uint16_t days, FuriHalRtcDateTime* datetime, uint16_t start_year) { + uint32_t timestamp = days * 24 * 60 * 60; + FuriHalRtcDateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime); + timestamp_to_datetime(timestamp, datetime); +} + +void from_minutes_to_datetime(uint32_t minutes, FuriHalRtcDateTime* datetime, uint16_t start_year) { + uint32_t timestamp = minutes * 60; + FuriHalRtcDateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime); + timestamp_to_datetime(timestamp, datetime); +} + +bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { + uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); + + FURI_LOG_I(TAG, "Transport departament: %x", transport_departament); + + uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); + if(layout_type == 0xE) { + layout_type = bit_lib_get_bits_16(block->data, 52, 9); + } else if(layout_type == 0xF) { + layout_type = bit_lib_get_bits_16(block->data, 52, 14); + } + + FURI_LOG_I(TAG, "Layout type %x", layout_type); + + uint16_t card_view = 0; + uint16_t card_type = 0; + uint32_t card_number = 0; + uint8_t card_layout = 0; + uint8_t card_layout2 = 0; + uint16_t card_use_before_date = 0; + uint16_t card_blank_type = 0; + uint32_t card_start_trip_minutes = 0; + uint8_t card_minutes_pass = 0; + uint32_t card_remaining_funds = 0; + uint16_t card_validator = 0; + uint8_t card_blocked = 0; + uint32_t card_hash = 0; + + switch(layout_type) { + case 0x02: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint8_t card_benefit_code = bit_lib_get_bits(block->data, 72, 8); //124 + uint32_t card_rfu1 = bit_lib_get_bits_32(block->data, 80, 32); //rfu1 + uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 + card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 177, 12); //403 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 189, 16); //402 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 + uint8_t card_start_trip_seconds = bit_lib_get_bits(block->data, 189, 6); //406 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + uint16_t card_use_with_date = bit_lib_get_bits_16(block->data, 189, 16); //205 + uint8_t card_route = bit_lib_get_bits(block->data, 205, 1); //424 + uint16_t card_validator1 = bit_lib_get_bits_16(block->data, 206, 15); //422.1 + card_validator = bit_lib_get_bits_16(block->data, 205, 16); //422 + uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 221, 16); //331 + uint8_t card_write_enabled = bit_lib_get_bits(block->data, 237, 1); //write_enabled + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 238, 2); //rfu2 + uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_benefit_code, + card_rfu1, + card_crc16, + card_blocked, + card_start_trip_time, + card_start_trip_date, + card_valid_from_date, + card_valid_by_date, + card_start_trip_seconds, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_use_with_date, + card_route, + card_validator1, + card_validator, + card_total_trips, + card_write_enabled, + card_rfu2, + card_crc16_2); + break; + } + case 0x06: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint8_t card_geozone_a = bit_lib_get_bits(block->data, 72, 4); //GeoZoneA + uint8_t card_geozone_b = bit_lib_get_bits(block->data, 76, 4); //GeoZoneB + card_blank_type = bit_lib_get_bits_16(block->data, 80, 10); //121. + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 90, 10); //122 + uint32_t card_rfu1 = bit_lib_get_bits_16(block->data, 100, 12); //rfu1 + uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 + card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 129, 12); //403 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 141, 16); //402 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 + uint16_t card_company = bit_lib_get_bits(block->data, 189, 4); //Company + uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 4); //422.1 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 197, 10); //321 + uint8_t card_units = bit_lib_get_bits(block->data, 207, 6); //Units + uint16_t card_validator2 = bit_lib_get_bits_16(block->data, 213, 10); //422.2 + uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 223, 16); //331 + uint8_t card_extended = bit_lib_get_bits(block->data, 239, 1); //123 + uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_geozone_a, + card_geozone_b, + card_blank_type, + card_type_of_extended, + card_rfu1, + card_crc16, + card_blocked, + card_start_trip_time, + card_start_trip_date, + card_valid_from_date, + card_valid_by_date, + card_company, + card_validator1, + card_remaining_trips, + card_units, + card_validator2, + card_total_trips, + card_extended, + card_crc16_2); + card_validator = card_validator1 * 1024 + card_validator2; + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date)*24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d of %d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_trips, + card_total_trips, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x08: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 153, 7); //rfu2 + uint8_t card_remaining_trips1 = bit_lib_get_bits(block->data, 160, 8); //321.1 + uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 168, 8); //321 + uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 2); //422.1 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 177, 15); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint32_t card_rfu3 = bit_lib_get_bits_32(block->data, 224, 32); //rfu3 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %lx %x %lx", + card_view, + card_type, + card_number, + card_use_before_date, + card_rfu1, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu2, + card_remaining_trips1, + card_remaining_trips, + card_validator1, + card_validator, + card_hash, + card_valid_from_date, + card_rfu3); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_trips, + card_validator); + break; + } + case 0x0A: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 64, 12); //311 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 76, 19); //314 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 95, 1); //301 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 96, 19); //405 + card_minutes_pass = bit_lib_get_bits(block->data, 119, 7); //412 + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 126, 2); //421.0 + uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 128, 8); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 136, 16); //422 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 152, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 154, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 156, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 158, 2); //421.4 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %lx %x %lx %x %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_use_before_date, + card_valid_from_date, + card_valid_for_minutes, + card_requires_activation, + card_start_trip_minutes, + card_minutes_pass, + card_transport_type_flag, + card_remaining_trips, + card_validator, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 2016); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x0C: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint16_t card_rfu2 = bit_lib_get_bits_16(block->data, 153, 13); //rfu2 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 + uint8_t card_transport_type = bit_lib_get_bits(block->data, 251, 2); //421 + uint8_t card_rfu3 = bit_lib_get_bits(block->data, 253, 2); //rfu3 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_rfu1, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu2, + card_remaining_trips, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type, + card_rfu3, + card_transfer_in_metro); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x0D: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint8_t card_rfu1 = bit_lib_get_bits(block->data, 56, 8); //rfu1 + card_use_before_date = bit_lib_get_bits_16(block->data, 64, 16); //202 + uint16_t card_valid_for_time = bit_lib_get_bits_16(block->data, 80, 11); //316 + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 91, 5); //rfu2 + uint16_t card_use_before_date2 = bit_lib_get_bits_16(block->data, 96, 16); //202.2 + uint16_t card_valid_for_time2 = bit_lib_get_bits_16(block->data, 123, 11); //316.2 + uint8_t card_rfu3 = bit_lib_get_bits(block->data, 123, 5); //rfu3 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint8_t card_rfu4 = bit_lib_get_bits(block->data, 153, 2); //rfu4 + uint8_t card_passage_5_minutes = bit_lib_get_bits(block->data, 155, 5); //413 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 160, 2); //421.1 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 162, 1); //431 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 163, 3); //433 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 251, 2); //421.2 + uint8_t card_rfu5 = bit_lib_get_bits(block->data, 253, 2); //rfu5 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_layout, + card_rfu1, + card_use_before_date, + card_valid_for_time, + card_rfu2, + card_use_before_date2, + card_valid_for_time2, + card_rfu3, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu4, + card_passage_5_minutes, + card_transport_type1, + card_passage_in_metro, + card_passages_ground_transport, + card_remaining_trips, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type2, + card_rfu5, + card_transfer_in_metro); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date - 1) * 24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x1C1: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121. + card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 144, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 160, 11); //403 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 171, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 173, 2); //421.2 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 177, 1); //432 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 178, 1); //431 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 179, 3); //433 + card_minutes_pass = bit_lib_get_bits(block->data, 185, 8); //412. + card_remaining_funds = bit_lib_get_bits_32(block->data, 196, 19) / 100; //322 + uint8_t card_fare_trip = bit_lib_get_bits(block->data, 215, 2); //441 + card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 + uint8_t card_zoo = bit_lib_get_bits(block->data, 218, 1); //zoo + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type1, + card_transport_type2, + card_transfer_in_metro, + card_passage_in_metro, + card_passages_ground_transport, + card_minutes_pass, + card_remaining_funds, + card_fare_trip, + card_blocked, + card_zoo, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C2: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 16); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 87, 10); //121. + uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 97, 16); //311 + uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 113, 9); //302 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 131, 20); //314 + card_minutes_pass = bit_lib_get_bits(block->data, 154, 8); //412. + uint8_t card_transport_type = bit_lib_get_bits(block->data, 163, 2); //421 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 165, 1); //431 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 166, 1); //432 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 167, 10); //321 + card_validator = bit_lib_get_bits_16(block->data, 177, 16); //422 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 196, 20); //404 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 216, 1); //301 + card_blocked = bit_lib_get_bits(block->data, 217, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 218, 1); //123 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_to_date, + card_activate_during, + card_valid_for_minutes, + card_minutes_pass, + card_transport_type, + card_passage_in_metro, + card_transfer_in_metro, + card_remaining_trips, + card_validator, + card_start_trip_neg_minutes, + card_requires_activation, + card_blocked, + card_extended, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_valid_to_date - 1) * 24 * 60 + card_valid_for_minutes - + card_start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2016); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C3: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202 + card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121 + card_remaining_funds = bit_lib_get_bits_32(block->data, 188, 22) / 100; //322 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 144, 23); //405 + uint8_t card_fare_trip = bit_lib_get_bits(block->data, 210, 2); //441 + card_minutes_pass = bit_lib_get_bits(block->data, 171, 7); //412 + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + card_blocked = bit_lib_get_bits(block->data, 212, 1); //303 + FURI_LOG_D( + TAG, + "Card view: %x, type: %x, number: %lx, layout: %x, layout2: %x, use before date: %x, blank type: %x, remaining funds: %lx, hash: %lx, validator: %x, start trip minutes: %lx, fare trip: %x, minutes pass: %x, transport type flag: %x, transport type1: %x, transport type2: %x, transport type3: %x, transport type4: %x, blocked: %x", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_remaining_funds, + card_hash, + card_validator, + card_start_trip_minutes, + card_fare_trip, + card_minutes_pass, + card_transport_type_flag, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_blocked); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 2016); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_funds, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C4: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. + uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 94, 13); //311 + uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 107, 9); //302 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 116, 10); //304 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 + card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 169, 10); //321 + card_validator = bit_lib_get_bits_16(block->data, 179, 16); //422 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 195, 20); //404 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 215, 1); //301 + card_blocked = bit_lib_get_bits(block->data, 216, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 217, 1); //123 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_to_date, + card_activate_during, + card_extension_counter, + card_valid_for_minutes, + card_minutes_pass, + card_transport_type_flag, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_remaining_trips, + card_validator, + card_start_trip_neg_minutes, + card_requires_activation, + card_blocked, + card_extended, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_use_before_date + 1) * 24 * 60 + card_valid_for_minutes - + card_start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2011); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C5: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 74, 10); //121. + uint32_t card_valid_to_time = bit_lib_get_bits_32(block->data, 84, 23); //317 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 107, 10); //304 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 128, 23); //405 + uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 151, 7); //414 + card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. + card_remaining_funds = bit_lib_get_bits_32(block->data, 167, 19) / 100; //322 + card_validator = bit_lib_get_bits_16(block->data, 186, 16); //422 + card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 + uint16_t card_route = bit_lib_get_bits_16(block->data, 204, 12); //424 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 216, 7); //433 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %lx %x %lx %x %x %lx %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_valid_to_time, + card_extension_counter, + card_start_trip_minutes, + card_metro_ride_with, + card_minutes_pass, + card_remaining_funds, + card_validator, + card_blocked, + card_route, + card_passages_ground_transport, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2019); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_start_trip_minutes - (24 * 60), &card_start_trip_minutes_s, 2019); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_funds, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C6: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. + uint32_t card_valid_from_date = bit_lib_get_bits_32(block->data, 94, 23); //311 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 117, 10); //304 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 148, 20); //404 + uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 168, 7); //414 + card_minutes_pass = bit_lib_get_bits(block->data, 175, 7); //412. + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 182, 7); //321 + card_validator = bit_lib_get_bits_16(block->data, 189, 16); //422 + card_blocked = bit_lib_get_bits(block->data, 205, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 206, 1); //123 + uint16_t card_route = bit_lib_get_bits_16(block->data, 212, 12); //424 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %lx %x %lx %lx %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_from_date, + card_extension_counter, + card_valid_for_minutes, + card_start_trip_neg_minutes, + card_metro_ride_with, + card_minutes_pass, + card_remaining_trips, + card_validator, + card_blocked, + card_extended, + card_route, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2019); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_valid_from_date + card_valid_for_minutes - card_start_trip_neg_minutes - 24 * 60, + &card_start_trip_minutes_s, + 2019); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x3CCB: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code + uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 + uint8_t card_interval = bit_lib_get_bits(block->data, 98, 4); //interval + uint16_t card_app_code1 = bit_lib_get_bits_16(block->data, 102, 16); //app_code1 + uint16_t card_hash1 = bit_lib_get_bits_16(block->data, 112, 16); //502.1 + uint16_t card_type1 = bit_lib_get_bits_16(block->data, 128, 10); //type1 + uint16_t card_app_code2 = bit_lib_get_bits_16(block->data, 138, 10); //app_code2 + uint16_t card_type2 = bit_lib_get_bits_16(block->data, 148, 10); //type2 + uint16_t card_app_code3 = bit_lib_get_bits_16(block->data, 158, 10); //app_code3 + uint16_t card_type3 = bit_lib_get_bits_16(block->data, 148, 10); //type3 + uint16_t card_app_code4 = bit_lib_get_bits_16(block->data, 168, 10); //app_code4 + uint16_t card_type4 = bit_lib_get_bits_16(block->data, 178, 10); //type4 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_tech_code, + card_use_before_date, + card_blank_type, + card_valid_to_minutes, + card_valid_by_date, + card_interval, + card_app_code1, + card_hash1, + card_type1, + card_app_code2, + card_type2, + card_app_code3, + card_type3, + card_app_code4, + card_type4, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_validator); + break; + } + case 0x3C0B: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code + uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 + uint16_t card_hash = bit_lib_get_bits_16(block->data, 112, 16); //502.1 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_layout, + card_tech_code, + card_use_before_date, + card_blank_type, + card_valid_to_minutes, + card_valid_by_date, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_validator); + break; + } + default: + return false; + } + + return true; +} + +static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClassicType type) { + bool success = true; + if(type == MfClassicType1k) { + config->data_sector = 15; + config->keys = social_moscow_1k_keys; + } else if(type == MfClassicType4k) { + config->data_sector = 15; + config->keys = social_moscow_4k_keys; + } else { + success = false; + } + + return success; +} + +static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + SocialMoscowCardConfig cfg = {}; + if(!social_moscow_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector); + + MfClassicKey key = {0}; + nfc_util_num2bytes(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + break; + } + FURI_LOG_D(TAG, "Verify success!"); + verified = true; + } while(false); + + return verified; +} + +static bool social_moscow_verify(Nfc* nfc) { + return social_moscow_verify_type(nfc, MfClassicType1k) || + social_moscow_verify_type(nfc, MfClassicType4k); +} + +static bool social_moscow_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType4k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + SocialMoscowCardConfig cfg = {}; + if(!social_moscow_get_card_config(&cfg, data->type)) break; + + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + SocialMoscowCardConfig cfg = {}; + if(!social_moscow_get_card_config(&cfg, data->type)) break; + + // Verify key + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector); + + const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + if(key != cfg.keys[cfg.data_sector].a) break; + + uint32_t card_code = bit_lib_get_bits_32(data->block[60].data, 8, 24); + uint8_t card_region = bit_lib_get_bits(data->block[60].data, 32, 8); + uint64_t card_number = bit_lib_get_bits_64(data->block[60].data, 40, 40); + uint8_t card_control = bit_lib_get_bits(data->block[60].data, 80, 4); + uint64_t omc_number = bit_lib_get_bits_64(data->block[21].data, 8, 64); + uint8_t year = data->block[60].data[11]; + uint8_t month = data->block[60].data[12]; + + FuriString* metro_result = furi_string_alloc(); + FuriString* ground_result = furi_string_alloc(); + bool result1 = parse_transport_block(&data->block[4], metro_result); + bool result2 = parse_transport_block(&data->block[16], ground_result); + furi_string_cat_printf( + parsed_data, + "\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n", + card_code, + card_region, + card_number, + card_control, + omc_number, + month, + year, + data->block[60].data[13], + data->block[60].data[14]); + if(result1) { + furi_string_cat_printf( + parsed_data, "\e#Metro\n%s\n", furi_string_get_cstr(metro_result)); + } + if(result2) { + furi_string_cat_printf( + parsed_data, "\e#Ground\n%s\n", furi_string_get_cstr(ground_result)); + } + furi_string_free(ground_result); + furi_string_free(metro_result); + parsed = result1 || result2; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin social_moscow_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = social_moscow_verify, + .read = social_moscow_read, + .parse = social_moscow_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor social_moscow_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &social_moscow_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* social_moscow_plugin_ep() { + return &social_moscow_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 7cf1e4dd8..800335290 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -1,10 +1,14 @@ #include "nfc_supported_card_plugin.h" +#include #include #include +#include #include #include +#include +#include #define TAG "Troika" @@ -60,14 +64,1432 @@ static const MfClassicKeyPair troika_4k_keys[] = { {.a = 0x518dc6eea089, .b = 0x97c64ac98ca4}, {.a = 0xbb52f8cce07f, .b = 0x6b6119752c70}, }; +#define TOPBIT(X) (1 << ((X)-1)) + +typedef enum { + BitLibParityEven, + BitLibParityOdd, + BitLibParityAlways0, + BitLibParityAlways1, +} BitLibParity; + +typedef struct { + const char mark; + const size_t start; + const size_t length; +} BitLibRegion; + +void bit_lib_push_bit(uint8_t* data, size_t data_size, bool bit) { + size_t last_index = data_size - 1; + + for(size_t i = 0; i < last_index; ++i) { + data[i] = (data[i] << 1) | ((data[i + 1] >> 7) & 1); + } + data[last_index] = (data[last_index] << 1) | bit; +} + +void bit_lib_set_bit(uint8_t* data, size_t position, bool bit) { + if(bit) { + data[position / 8] |= 1UL << (7 - (position % 8)); + } else { + data[position / 8] &= ~(1UL << (7 - (position % 8))); + } +} + +void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t length) { + furi_check(length <= 8); + furi_check(length > 0); + + for(uint8_t i = 0; i < length; ++i) { + uint8_t shift = (length - 1) - i; + bit_lib_set_bit(data, position + i, (byte >> shift) & 1); //-V610 + } +} + +bool bit_lib_get_bit(const uint8_t* data, size_t position) { + return (data[position / 8] >> (7 - (position % 8))) & 1; +} + +uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length) { + uint8_t shift = position % 8; + if(shift == 0) { + return data[position / 8] >> (8 - length); + } else { + // TODO fix read out of bounds + uint8_t value = (data[position / 8] << (shift)); + value |= data[position / 8 + 1] >> (8 - shift); + value = value >> (8 - length); + return value; + } +} + +uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t length) { + uint16_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } + return value; +} + +uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length) { + uint32_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else if(length <= 16) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } else if(length <= 24) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, length - 16); + } else { + value = (uint32_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint32_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint32_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= bit_lib_get_bits(data, position + 24, length - 24); + } + + return value; +} + +uint64_t bit_lib_get_bits_64(const uint8_t* data, size_t position, uint8_t length) { + uint64_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else if(length <= 16) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } else if(length <= 24) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, length - 16); + } else if(length <= 32) { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= bit_lib_get_bits(data, position + 24, length - 24); + } else { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= (uint64_t)bit_lib_get_bits(data, position + 24, 8) << (length - 32); + value |= (uint64_t)bit_lib_get_bits(data, position + 32, 8) << (length - 40); + value |= (uint64_t)bit_lib_get_bits(data, position + 40, 8) << (length - 48); + value |= (uint64_t)bit_lib_get_bits(data, position + 48, 8) << (length - 56); + value |= (uint64_t)bit_lib_get_bits(data, position + 56, 8) << (length - 64); + value |= bit_lib_get_bits(data, position + 64, length - 64); + } + + return value; +} + +bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity) { +#if !defined __GNUC__ +#error Please, implement parity test for non-GCC compilers +#else + switch(parity) { + case BitLibParityEven: + return __builtin_parity(bits); + case BitLibParityOdd: + return !__builtin_parity(bits); + default: + furi_crash("Unknown parity"); + } +#endif +} + +bool bit_lib_test_parity( + const uint8_t* bits, + size_t position, + uint8_t length, + BitLibParity parity, + uint8_t parity_length) { + uint32_t parity_block; + bool result = true; + const size_t parity_blocks_count = length / parity_length; + + for(size_t i = 0; i < parity_blocks_count; ++i) { + switch(parity) { + case BitLibParityEven: + case BitLibParityOdd: + parity_block = bit_lib_get_bits_32(bits, position + i * parity_length, parity_length); + if(!bit_lib_test_parity_32(parity_block, parity)) { + result = false; + } + break; + case BitLibParityAlways0: + if(bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { + result = false; + } + break; + case BitLibParityAlways1: + if(!bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { + result = false; + } + break; + } + + if(!result) break; + } + return result; +} + +size_t bit_lib_add_parity( + const uint8_t* data, + size_t position, + uint8_t* dest, + size_t dest_position, + uint8_t source_length, + uint8_t parity_length, + BitLibParity parity) { + uint32_t parity_word = 0; + size_t j = 0, bit_count = 0; + for(int word = 0; word < source_length; word += parity_length - 1) { + for(int bit = 0; bit < parity_length - 1; bit++) { + parity_word = (parity_word << 1) | bit_lib_get_bit(data, position + word + bit); + bit_lib_set_bit( + dest, dest_position + j++, bit_lib_get_bit(data, position + word + bit)); + } + // if parity fails then return 0 + switch(parity) { + case BitLibParityAlways0: + bit_lib_set_bit(dest, dest_position + j++, 0); + break; // marker bit which should be a 0 + case BitLibParityAlways1: + bit_lib_set_bit(dest, dest_position + j++, 1); + break; // marker bit which should be a 1 + default: + bit_lib_set_bit( + dest, + dest_position + j++, + (bit_lib_test_parity_32(parity_word, BitLibParityOdd) ^ parity) ^ 1); + break; + } + bit_count += parity_length; + parity_word = 0; + } + // if we got here then all the parities passed + // return bit count + return bit_count; +} + +size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n) { + size_t counter = 0; + size_t result_counter = 0; + uint8_t bit_buffer = 0; + uint8_t bit_counter = 0; + + while(counter < length) { + if((counter + 1) % n != 0) { + bit_buffer = (bit_buffer << 1) | bit_lib_get_bit(data, position + counter); + bit_counter++; + } + + if(bit_counter == 8) { + bit_lib_set_bits(data, position + result_counter, bit_buffer, 8); + bit_counter = 0; + bit_buffer = 0; + result_counter += 8; + } + counter++; + } + + if(bit_counter != 0) { + bit_lib_set_bits(data, position + result_counter, bit_buffer, bit_counter); + result_counter += bit_counter; + } + return result_counter; +} + +void bit_lib_copy_bits( + uint8_t* data, + size_t position, + size_t length, + const uint8_t* source, + size_t source_position) { + for(size_t i = 0; i < length; ++i) { + bit_lib_set_bit(data, position + i, bit_lib_get_bit(source, source_position + i)); + } +} + +void bit_lib_reverse_bits(uint8_t* data, size_t position, uint8_t length) { + size_t i = 0; + size_t j = length - 1; + + while(i < j) { + bool tmp = bit_lib_get_bit(data, position + i); + bit_lib_set_bit(data, position + i, bit_lib_get_bit(data, position + j)); + bit_lib_set_bit(data, position + j, tmp); + i++; + j--; + } +} + +uint8_t bit_lib_get_bit_count(uint32_t data) { +#if defined __GNUC__ + return __builtin_popcountl(data); +#else +#error Please, implement popcount for non-GCC compilers +#endif +} + +void bit_lib_print_bits(const uint8_t* data, size_t length) { + for(size_t i = 0; i < length; ++i) { + printf("%u", bit_lib_get_bit(data, i)); + } +} + +void bit_lib_print_regions( + const BitLibRegion* regions, + size_t region_count, + const uint8_t* data, + size_t length) { + // print data + bit_lib_print_bits(data, length); + printf("\r\n"); + + // print regions + for(size_t c = 0; c < length; ++c) { + bool print = false; + + for(size_t i = 0; i < region_count; i++) { + if(regions[i].start <= c && c < regions[i].start + regions[i].length) { + print = true; + printf("%c", regions[i].mark); + break; + } + } + + if(!print) { + printf(" "); + } + } + printf("\r\n"); + + // print regions data + for(size_t c = 0; c < length; ++c) { + bool print = false; + + for(size_t i = 0; i < region_count; i++) { + if(regions[i].start <= c && c < regions[i].start + regions[i].length) { + print = true; + printf("%u", bit_lib_get_bit(data, c)); + break; + } + } + + if(!print) { + printf(" "); + } + } + printf("\r\n"); +} + +uint16_t bit_lib_reverse_16_fast(uint16_t data) { + uint16_t result = 0; + result |= (data & 0x8000) >> 15; + result |= (data & 0x4000) >> 13; + result |= (data & 0x2000) >> 11; + result |= (data & 0x1000) >> 9; + result |= (data & 0x0800) >> 7; + result |= (data & 0x0400) >> 5; + result |= (data & 0x0200) >> 3; + result |= (data & 0x0100) >> 1; + result |= (data & 0x0080) << 1; + result |= (data & 0x0040) << 3; + result |= (data & 0x0020) << 5; + result |= (data & 0x0010) << 7; + result |= (data & 0x0008) << 9; + result |= (data & 0x0004) << 11; + result |= (data & 0x0002) << 13; + result |= (data & 0x0001) << 15; + return result; +} + +uint8_t bit_lib_reverse_8_fast(uint8_t byte) { + byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4; + byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2; + byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1; + return byte; +} + +uint16_t bit_lib_crc8( + uint8_t const* data, + size_t data_size, + uint8_t polynom, + uint8_t init, + bool ref_in, + bool ref_out, + uint8_t xor_out) { + uint8_t crc = init; + + for(size_t i = 0; i < data_size; ++i) { + uint8_t byte = data[i]; + if(ref_in) bit_lib_reverse_bits(&byte, 0, 8); + crc ^= byte; + + for(size_t j = 8; j > 0; --j) { + if(crc & TOPBIT(8)) { + crc = (crc << 1) ^ polynom; + } else { + crc = (crc << 1); + } + } + } + + if(ref_out) bit_lib_reverse_bits(&crc, 0, 8); + crc ^= xor_out; + + return crc; +} + +uint16_t bit_lib_crc16( + uint8_t const* data, + size_t data_size, + uint16_t polynom, + uint16_t init, + bool ref_in, + bool ref_out, + uint16_t xor_out) { + uint16_t crc = init; + + for(size_t i = 0; i < data_size; ++i) { + uint8_t byte = data[i]; + if(ref_in) byte = bit_lib_reverse_16_fast(byte) >> 8; + + for(size_t j = 0; j < 8; ++j) { + bool c15 = (crc >> 15 & 1); + bool bit = (byte >> (7 - j) & 1); + crc <<= 1; + if(c15 ^ bit) crc ^= polynom; + } + } + + if(ref_out) crc = bit_lib_reverse_16_fast(crc); + crc ^= xor_out; + + return crc; +} + +#define FURI_HAL_RTC_SECONDS_PER_MINUTE 60 +#define FURI_HAL_RTC_SECONDS_PER_HOUR (FURI_HAL_RTC_SECONDS_PER_MINUTE * 60) +#define FURI_HAL_RTC_SECONDS_PER_DAY (FURI_HAL_RTC_SECONDS_PER_HOUR * 24) +#define FURI_HAL_RTC_EPOCH_START_YEAR 1970 +#define FURI_HAL_RTC_IS_LEAP_YEAR(year) \ + ((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0)) + +void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { + uint32_t days = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; + uint32_t seconds_in_day = timestamp % FURI_HAL_RTC_SECONDS_PER_DAY; + + datetime->year = FURI_HAL_RTC_EPOCH_START_YEAR; + + while(days >= furi_hal_rtc_get_days_per_year(datetime->year)) { + days -= furi_hal_rtc_get_days_per_year(datetime->year); + (datetime->year)++; + } + + datetime->month = 1; + while(days >= furi_hal_rtc_get_days_per_month( + FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month)) { + days -= furi_hal_rtc_get_days_per_month( + FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month); + (datetime->month)++; + } + + datetime->day = days + 1; + datetime->hour = seconds_in_day / FURI_HAL_RTC_SECONDS_PER_HOUR; + datetime->minute = + (seconds_in_day % FURI_HAL_RTC_SECONDS_PER_HOUR) / FURI_HAL_RTC_SECONDS_PER_MINUTE; + datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE; +} + +void from_days_to_datetime(uint16_t days, FuriHalRtcDateTime* datetime, uint16_t start_year) { + uint32_t timestamp = days * 24 * 60 * 60; + FuriHalRtcDateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime); + timestamp_to_datetime(timestamp, datetime); +} + +void from_minutes_to_datetime(uint32_t minutes, FuriHalRtcDateTime* datetime, uint16_t start_year) { + uint32_t timestamp = minutes * 60; + FuriHalRtcDateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime); + timestamp_to_datetime(timestamp, datetime); +} + +bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { + uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); + + FURI_LOG_I(TAG, "Transport departament: %x", transport_departament); + + uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); + if(layout_type == 0xE) { + layout_type = bit_lib_get_bits_16(block->data, 52, 9); + } else if(layout_type == 0xF) { + layout_type = bit_lib_get_bits_16(block->data, 52, 14); + } + + FURI_LOG_I(TAG, "Layout type %x", layout_type); + + uint16_t card_view = 0; + uint16_t card_type = 0; + uint32_t card_number = 0; + uint8_t card_layout = 0; + uint8_t card_layout2 = 0; + uint16_t card_use_before_date = 0; + uint16_t card_blank_type = 0; + uint32_t card_start_trip_minutes = 0; + uint8_t card_minutes_pass = 0; + uint32_t card_remaining_funds = 0; + uint16_t card_validator = 0; + uint8_t card_blocked = 0; + uint32_t card_hash = 0; + + switch(layout_type) { + case 0x02: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint8_t card_benefit_code = bit_lib_get_bits(block->data, 72, 8); //124 + uint32_t card_rfu1 = bit_lib_get_bits_32(block->data, 80, 32); //rfu1 + uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 + card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 177, 12); //403 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 189, 16); //402 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 + uint8_t card_start_trip_seconds = bit_lib_get_bits(block->data, 189, 6); //406 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + uint16_t card_use_with_date = bit_lib_get_bits_16(block->data, 189, 16); //205 + uint8_t card_route = bit_lib_get_bits(block->data, 205, 1); //424 + uint16_t card_validator1 = bit_lib_get_bits_16(block->data, 206, 15); //422.1 + card_validator = bit_lib_get_bits_16(block->data, 205, 16); //422 + uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 221, 16); //331 + uint8_t card_write_enabled = bit_lib_get_bits(block->data, 237, 1); //write_enabled + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 238, 2); //rfu2 + uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_benefit_code, + card_rfu1, + card_crc16, + card_blocked, + card_start_trip_time, + card_start_trip_date, + card_valid_from_date, + card_valid_by_date, + card_start_trip_seconds, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_use_with_date, + card_route, + card_validator1, + card_validator, + card_total_trips, + card_write_enabled, + card_rfu2, + card_crc16_2); + if(card_valid_by_date == 0) { + return false; + } + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date)*24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips: %d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_total_trips, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x06: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint8_t card_geozone_a = bit_lib_get_bits(block->data, 72, 4); //GeoZoneA + uint8_t card_geozone_b = bit_lib_get_bits(block->data, 76, 4); //GeoZoneB + card_blank_type = bit_lib_get_bits_16(block->data, 80, 10); //121. + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 90, 10); //122 + uint32_t card_rfu1 = bit_lib_get_bits_16(block->data, 100, 12); //rfu1 + uint16_t card_crc16 = bit_lib_get_bits_16(block->data, 112, 16); //501.1 + card_blocked = bit_lib_get_bits(block->data, 128, 1); //303 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 129, 12); //403 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 141, 16); //402 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 157, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 173, 16); //312 + uint16_t card_company = bit_lib_get_bits(block->data, 189, 4); //Company + uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 4); //422.1 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 197, 10); //321 + uint8_t card_units = bit_lib_get_bits(block->data, 207, 6); //Units + uint16_t card_validator2 = bit_lib_get_bits_16(block->data, 213, 10); //422.2 + uint16_t card_total_trips = bit_lib_get_bits_16(block->data, 223, 16); //331 + uint8_t card_extended = bit_lib_get_bits(block->data, 239, 1); //123 + uint16_t card_crc16_2 = bit_lib_get_bits_16(block->data, 240, 16); //501.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_geozone_a, + card_geozone_b, + card_blank_type, + card_type_of_extended, + card_rfu1, + card_crc16, + card_blocked, + card_start_trip_time, + card_start_trip_date, + card_valid_from_date, + card_valid_by_date, + card_company, + card_validator1, + card_remaining_trips, + card_units, + card_validator2, + card_total_trips, + card_extended, + card_crc16_2); + card_validator = card_validator1 * 1024 + card_validator2; + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date)*24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d of %d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_trips, + card_total_trips, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x08: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 153, 7); //rfu2 + uint8_t card_remaining_trips1 = bit_lib_get_bits(block->data, 160, 8); //321.1 + uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 168, 8); //321 + uint8_t card_validator1 = bit_lib_get_bits(block->data, 193, 2); //422.1 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 177, 15); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint32_t card_rfu3 = bit_lib_get_bits_32(block->data, 224, 32); //rfu3 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %lx %x %lx", + card_view, + card_type, + card_number, + card_use_before_date, + card_rfu1, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu2, + card_remaining_trips1, + card_remaining_trips, + card_validator1, + card_validator, + card_hash, + card_valid_from_date, + card_rfu3); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_trips, + card_validator); + break; + } + case 0x0A: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 64, 12); //311 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 76, 19); //314 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 95, 1); //301 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 96, 19); //405 + card_minutes_pass = bit_lib_get_bits(block->data, 119, 7); //412 + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 126, 2); //421.0 + uint8_t card_remaining_trips = bit_lib_get_bits(block->data, 128, 8); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 136, 16); //422 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 152, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 154, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 156, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 158, 2); //421.4 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %lx %x %lx %x %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_use_before_date, + card_valid_from_date, + card_valid_for_minutes, + card_requires_activation, + card_start_trip_minutes, + card_minutes_pass, + card_transport_type_flag, + card_remaining_trips, + card_validator, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 2016); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x0C: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_use_before_date = bit_lib_get_bits_16(block->data, 56, 16); //202 + uint64_t card_rfu1 = bit_lib_get_bits_64(block->data, 72, 56); //rfu1 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint16_t card_rfu2 = bit_lib_get_bits_16(block->data, 153, 13); //rfu2 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 + uint8_t card_transport_type = bit_lib_get_bits(block->data, 251, 2); //421 + uint8_t card_rfu3 = bit_lib_get_bits(block->data, 253, 2); //rfu3 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %llx %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_use_before_date, + card_rfu1, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu2, + card_remaining_trips, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type, + card_rfu3, + card_transfer_in_metro); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date)*24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x0D: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint8_t card_rfu1 = bit_lib_get_bits(block->data, 56, 8); //rfu1 + card_use_before_date = bit_lib_get_bits_16(block->data, 64, 16); //202 + uint16_t card_valid_for_time = bit_lib_get_bits_16(block->data, 80, 11); //316 + uint8_t card_rfu2 = bit_lib_get_bits(block->data, 91, 5); //rfu2 + uint16_t card_use_before_date2 = bit_lib_get_bits_16(block->data, 96, 16); //202.2 + uint16_t card_valid_for_time2 = bit_lib_get_bits_16(block->data, 123, 11); //316.2 + uint8_t card_rfu3 = bit_lib_get_bits(block->data, 123, 5); //rfu3 + uint16_t card_valid_from_date = bit_lib_get_bits_16(block->data, 128, 16); //311 + uint8_t card_valid_for_days = bit_lib_get_bits(block->data, 144, 8); //313 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 152, 1); //301 + uint8_t card_rfu4 = bit_lib_get_bits(block->data, 153, 2); //rfu4 + uint8_t card_passage_5_minutes = bit_lib_get_bits(block->data, 155, 5); //413 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 160, 2); //421.1 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 162, 1); //431 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 163, 3); //433 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 166, 10); //321 + uint16_t card_validator = bit_lib_get_bits_16(block->data, 176, 16); //422 + card_hash = bit_lib_get_bits_32(block->data, 192, 32); //502 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 224, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 240, 11); //403 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 251, 2); //421.2 + uint8_t card_rfu5 = bit_lib_get_bits(block->data, 253, 2); //rfu5 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 255, 1); //432 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_layout, + card_rfu1, + card_use_before_date, + card_valid_for_time, + card_rfu2, + card_use_before_date2, + card_valid_for_time2, + card_rfu3, + card_valid_from_date, + card_valid_for_days, + card_requires_activation, + card_rfu4, + card_passage_5_minutes, + card_transport_type1, + card_passage_in_metro, + card_passages_ground_transport, + card_remaining_trips, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type2, + card_rfu5, + card_transfer_in_metro); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_start_trip_date)*24 * 60 + card_start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_remaining_trips, + card_validator); + break; + } + case 0x1C1: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121. + card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 + uint16_t card_start_trip_date = bit_lib_get_bits_16(block->data, 144, 16); //402 + uint16_t card_start_trip_time = bit_lib_get_bits_16(block->data, 160, 11); //403 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 171, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 173, 2); //421.2 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 177, 1); //432 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 178, 1); //431 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 179, 3); //433 + card_minutes_pass = bit_lib_get_bits(block->data, 185, 8); //412. + card_remaining_funds = bit_lib_get_bits_32(block->data, 196, 19) / 100; //322 + uint8_t card_fare_trip = bit_lib_get_bits(block->data, 215, 2); //441 + card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 + uint8_t card_zoo = bit_lib_get_bits(block->data, 218, 1); //zoo + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_validator, + card_start_trip_date, + card_start_trip_time, + card_transport_type1, + card_transport_type2, + card_transfer_in_metro, + card_passage_in_metro, + card_passages_ground_transport, + card_minutes_pass, + card_remaining_funds, + card_fare_trip, + card_blocked, + card_zoo, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 1992); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C2: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 16); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 87, 10); //121. + uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 97, 16); //311 + uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 113, 9); //302 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 131, 20); //314 + card_minutes_pass = bit_lib_get_bits(block->data, 154, 8); //412. + uint8_t card_transport_type = bit_lib_get_bits(block->data, 163, 2); //421 + uint8_t card_passage_in_metro = bit_lib_get_bits(block->data, 165, 1); //431 + uint8_t card_transfer_in_metro = bit_lib_get_bits(block->data, 166, 1); //432 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 167, 10); //321 + card_validator = bit_lib_get_bits_16(block->data, 177, 16); //422 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 196, 20); //404 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 216, 1); //301 + card_blocked = bit_lib_get_bits(block->data, 217, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 218, 1); //123 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_to_date, + card_activate_during, + card_valid_for_minutes, + card_minutes_pass, + card_transport_type, + card_passage_in_metro, + card_transfer_in_metro, + card_remaining_trips, + card_validator, + card_start_trip_neg_minutes, + card_requires_activation, + card_blocked, + card_extended, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_valid_to_date)*24 * 60 + card_valid_for_minutes - card_start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2016); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C3: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202 + card_blank_type = bit_lib_get_bits_16(block->data, 77, 10); //121 + card_remaining_funds = bit_lib_get_bits_32(block->data, 188, 22) / 100; //322 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + card_validator = bit_lib_get_bits_16(block->data, 128, 16); //422 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 144, 23); //405 + uint8_t card_fare_trip = bit_lib_get_bits(block->data, 210, 2); //441 + card_minutes_pass = bit_lib_get_bits(block->data, 171, 7); //412 + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + card_blocked = bit_lib_get_bits(block->data, 212, 1); //303 + FURI_LOG_D( + TAG, + "Card view: %x, type: %x, number: %lx, layout: %x, layout2: %x, use before date: %x, blank type: %x, remaining funds: %lx, hash: %lx, validator: %x, start trip minutes: %lx, fare trip: %x, minutes pass: %x, transport type flag: %x, transport type1: %x, transport type2: %x, transport type3: %x, transport type4: %x, blocked: %x", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_remaining_funds, + card_hash, + card_validator, + card_start_trip_minutes, + card_fare_trip, + card_minutes_pass, + card_transport_type_flag, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_blocked); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 2016); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_funds, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C4: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. + uint16_t card_valid_to_date = bit_lib_get_bits_16(block->data, 94, 13); //311 + uint16_t card_activate_during = bit_lib_get_bits_16(block->data, 107, 9); //302 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 116, 10); //304 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 + card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. + uint8_t card_transport_type_flag = bit_lib_get_bits(block->data, 178, 2); //421.0 + uint8_t card_transport_type1 = bit_lib_get_bits(block->data, 180, 2); //421.1 + uint8_t card_transport_type2 = bit_lib_get_bits(block->data, 182, 2); //421.2 + uint8_t card_transport_type3 = bit_lib_get_bits(block->data, 184, 2); //421.3 + uint8_t card_transport_type4 = bit_lib_get_bits(block->data, 186, 2); //421.4 + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 169, 10); //321 + card_validator = bit_lib_get_bits_16(block->data, 179, 16); //422 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 195, 20); //404 + uint8_t card_requires_activation = bit_lib_get_bits(block->data, 215, 1); //301 + card_blocked = bit_lib_get_bits(block->data, 216, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 217, 1); //123 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_to_date, + card_activate_during, + card_extension_counter, + card_valid_for_minutes, + card_minutes_pass, + card_transport_type_flag, + card_transport_type1, + card_transport_type2, + card_transport_type3, + card_transport_type4, + card_remaining_trips, + card_validator, + card_start_trip_neg_minutes, + card_requires_activation, + card_blocked, + card_extended, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2016); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (card_use_before_date + 1) * 24 * 60 + card_valid_for_minutes - + card_start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2011); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C5: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + card_use_before_date = bit_lib_get_bits_16(block->data, 61, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 74, 10); //121. + uint32_t card_valid_to_time = bit_lib_get_bits_32(block->data, 84, 23); //317 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 107, 10); //304 + card_start_trip_minutes = bit_lib_get_bits_32(block->data, 128, 23); //405 + uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 151, 7); //414 + card_minutes_pass = bit_lib_get_bits(block->data, 158, 7); //412. + card_remaining_funds = bit_lib_get_bits_32(block->data, 167, 19) / 100; //322 + card_validator = bit_lib_get_bits_16(block->data, 186, 16); //422 + card_blocked = bit_lib_get_bits(block->data, 202, 1); //303 + uint16_t card_route = bit_lib_get_bits_16(block->data, 204, 12); //424 + uint8_t card_passages_ground_transport = bit_lib_get_bits(block->data, 216, 7); //433 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %lx %x %lx %x %x %lx %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_use_before_date, + card_blank_type, + card_valid_to_time, + card_extension_counter, + card_start_trip_minutes, + card_metro_ride_with, + card_minutes_pass, + card_remaining_funds, + card_validator, + card_blocked, + card_route, + card_passages_ground_transport, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2019); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 2019); + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_remaining_funds, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x1C6: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + card_layout2 = bit_lib_get_bits(block->data, 56, 5); //112 + uint16_t card_type_of_extended = bit_lib_get_bits_16(block->data, 61, 10); //122 + card_use_before_date = bit_lib_get_bits_16(block->data, 71, 13); //202. + card_blank_type = bit_lib_get_bits_16(block->data, 84, 10); //121. + uint32_t card_valid_from_date = bit_lib_get_bits_32(block->data, 94, 23); //311 + uint16_t card_extension_counter = bit_lib_get_bits_16(block->data, 117, 10); //304 + uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->data, 128, 20); //314 + uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 148, 20); //404 + uint8_t card_metro_ride_with = bit_lib_get_bits(block->data, 168, 7); //414 + card_minutes_pass = bit_lib_get_bits(block->data, 175, 7); //412. + uint16_t card_remaining_trips = bit_lib_get_bits_16(block->data, 182, 7); //321 + card_validator = bit_lib_get_bits_16(block->data, 189, 16); //422 + card_blocked = bit_lib_get_bits(block->data, 205, 1); //303 + uint8_t card_extended = bit_lib_get_bits(block->data, 206, 1); //123 + uint16_t card_route = bit_lib_get_bits_16(block->data, 212, 12); //424 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %lx %x %lx %lx %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_layout2, + card_type_of_extended, + card_use_before_date, + card_blank_type, + card_valid_from_date, + card_extension_counter, + card_valid_for_minutes, + card_start_trip_neg_minutes, + card_metro_ride_with, + card_minutes_pass, + card_remaining_trips, + card_validator, + card_blocked, + card_extended, + card_route, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2019); + + FuriHalRtcDateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + card_valid_from_date + card_valid_for_minutes - card_start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2019); //-time + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute, + card_validator); + break; + } + case 0x3CCB: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code + uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 + uint8_t card_interval = bit_lib_get_bits(block->data, 98, 4); //interval + uint16_t card_app_code1 = bit_lib_get_bits_16(block->data, 102, 16); //app_code1 + uint16_t card_hash1 = bit_lib_get_bits_16(block->data, 112, 16); //502.1 + uint16_t card_type1 = bit_lib_get_bits_16(block->data, 128, 10); //type1 + uint16_t card_app_code2 = bit_lib_get_bits_16(block->data, 138, 10); //app_code2 + uint16_t card_type2 = bit_lib_get_bits_16(block->data, 148, 10); //type2 + uint16_t card_app_code3 = bit_lib_get_bits_16(block->data, 158, 10); //app_code3 + uint16_t card_type3 = bit_lib_get_bits_16(block->data, 148, 10); //type3 + uint16_t card_app_code4 = bit_lib_get_bits_16(block->data, 168, 10); //app_code4 + uint16_t card_type4 = bit_lib_get_bits_16(block->data, 178, 10); //type4 + card_hash = bit_lib_get_bits_32(block->data, 224, 32); //502.2 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %lx", + card_view, + card_type, + card_number, + card_layout, + card_tech_code, + card_use_before_date, + card_blank_type, + card_valid_to_minutes, + card_valid_by_date, + card_interval, + card_app_code1, + card_hash1, + card_type1, + card_app_code2, + card_type2, + card_app_code3, + card_type3, + card_app_code4, + card_type4, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_validator); + break; + } + case 0x3C0B: { + card_view = bit_lib_get_bits_16(block->data, 0, 10); //101 + card_type = bit_lib_get_bits_16(block->data, 10, 10); //102 + card_number = bit_lib_get_bits_32(block->data, 20, 32); //201 + card_layout = bit_lib_get_bits(block->data, 52, 4); //111 + uint16_t card_tech_code = bit_lib_get_bits_32(block->data, 56, 10); //tech_code + uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->data, 66, 16); //311 + uint16_t card_valid_by_date = bit_lib_get_bits_16(block->data, 82, 16); //312 + uint16_t card_hash = bit_lib_get_bits_16(block->data, 112, 16); //502.1 + + FURI_LOG_D( + TAG, + "%x %x %lx %x %x %x %x %x %x %x", + card_view, + card_type, + card_number, + card_layout, + card_tech_code, + card_use_before_date, + card_blank_type, + card_valid_to_minutes, + card_valid_by_date, + card_hash); + FuriHalRtcDateTime card_use_before_date_s = {0}; + from_days_to_datetime(card_valid_by_date, &card_use_before_date_s, 1992); + + furi_string_printf( + result, + "Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d", + card_number, + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year, + card_validator); + break; + } + default: + return false; + } + + return true; +} + static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { bool success = true; if(type == MfClassicType1k) { - config->data_sector = 8; + config->data_sector = 11; config->keys = troika_1k_keys; } else if(type == MfClassicType4k) { - config->data_sector = 4; + config->data_sector = 11; config->keys = troika_4k_keys; } else { success = false; @@ -96,7 +1518,7 @@ static bool troika_verify_type(Nfc* nfc, MfClassicType type) { FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); break; } - + FURI_LOG_D(TAG, "Verify success!"); verified = true; } while(false); @@ -171,23 +1593,28 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key != cfg.keys[cfg.data_sector].a) break; - // Parse data - const uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); - - const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[5]; - uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; - temp_ptr = &data->block[start_block_num].data[2]; - - uint32_t number = 0; - for(size_t i = 1; i < 5; i++) { - number <<= 8; - number |= temp_ptr[i]; + FuriString* metro_result = furi_string_alloc(); + FuriString* ground_result = furi_string_alloc(); + FuriString* tat_result = furi_string_alloc(); + bool result1 = parse_transport_block(&data->block[32], metro_result); + bool result2 = parse_transport_block(&data->block[28], ground_result); + bool result3 = parse_transport_block(&data->block[16], tat_result); + furi_string_cat_printf(parsed_data, "\e#Troyka card\n"); + if(result1) { + furi_string_cat_printf( + parsed_data, "\e#Metro\n%s\n", furi_string_get_cstr(metro_result)); } - number >>= 4; - number |= (temp_ptr[0] & 0xf) << 28; - - furi_string_printf(parsed_data, "\e#Troika\nNum: %lu\nBalance: %u RUR", number, balance); - parsed = true; + if(result2) { + furi_string_cat_printf( + parsed_data, "\e#Ediniy\n%s\n", furi_string_get_cstr(ground_result)); + } + if(result3) { + furi_string_cat_printf(parsed_data, "\e#TAT\n%s\n", furi_string_get_cstr(tat_result)); + } + furi_string_free(tat_result); + furi_string_free(ground_result); + furi_string_free(metro_result); + parsed = result1 || result2 || result3; } while(false); return parsed; @@ -211,4 +1638,4 @@ static const FlipperAppPluginDescriptor troika_plugin_descriptor = { /* Plugin entry point - must return a pointer to const descriptor */ const FlipperAppPluginDescriptor* troika_plugin_ep() { return &troika_plugin_descriptor; -} +} \ No newline at end of file diff --git a/applications/main/nfc/plugins/supported_cards/umarsh.c b/applications/main/nfc/plugins/supported_cards/umarsh.c new file mode 100644 index 000000000..a85c056f6 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/umarsh.c @@ -0,0 +1,155 @@ +/* + * Parser for Umarsh card (Russia). + * + * Copyright 2023 Leptoptilos + * Thanks https://github.com/krolchonok for the provided dumps and their analysis + * + * Note: All meaningful data is stored in sectors 0, 8 and 12, reading data + * from which is possible only with the B key. The key B for these sectors + * is unique for each card. To get it, you should use a nested attack. + * More info about Umarsh cards: https://github.com/metrodroid/metrodroid/wiki/Umarsh + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "core/core_defines.h" +#include "nfc_supported_card_plugin.h" + +#include "protocols/mf_classic/mf_classic.h" +#include + +#include +#include +#include +#include +#include +#include + +#define TAG "Umarsh" + +bool parse_datetime(uint16_t date, FuriHalRtcDateTime* result) { + result->year = 2000 + (date >> 9); + result->month = date >> 5 & 0x0F; + result->day = date & 0x1F; + return (date != 0); +} + +static bool umarsh_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + if(data->type != MfClassicType1k) break; + + const uint8_t ticket_sector = 8; + + const uint8_t ticket_sector_start_block_number = + mf_classic_get_first_block_num_of_sector(ticket_sector); + + // Validate specific for Umarsh ticket sector header + const uint8_t* block_start_ptr = &data->block[ticket_sector_start_block_number].data[0]; + + const uint32_t header_part_0 = nfc_util_bytes2num(block_start_ptr, 4); + const uint32_t header_part_1 = nfc_util_bytes2num(block_start_ptr + 4, 4); + if((header_part_0 + header_part_1) != 0xFFFFFFFF) break; + + // Data parsing from block 1 + block_start_ptr = &data->block[ticket_sector_start_block_number + 1].data[0]; + const uint16_t expiry_date = nfc_util_bytes2num(block_start_ptr + 1, 2); + const uint8_t region_number = (((block_start_ptr[8] >> 5) & 0x07) << 4) | + (block_start_ptr[12] & 0x0F); + const uint8_t refill_counter = nfc_util_bytes2num(block_start_ptr + 7, 1); + const uint32_t card_number = nfc_util_bytes2num(block_start_ptr + 8, 4) & 0x3FFFFFFF; + + if(card_number == 0) break; + + // Data parsing from block 2 + block_start_ptr = &data->block[ticket_sector_start_block_number + 2].data[0]; + const uint16_t valid_to = nfc_util_bytes2num(block_start_ptr, 2); + const uint32_t terminal_number = nfc_util_bytes2num(block_start_ptr + 3, 3); + const uint16_t last_refill_date = nfc_util_bytes2num(block_start_ptr + 6, 2); + const uint16_t balance_rub = (nfc_util_bytes2num(block_start_ptr + 8, 2)) & 0x7FFF; + const uint8_t balance_kop = nfc_util_bytes2num(block_start_ptr + 10, 1) & 0x7F; + + FuriHalRtcDateTime expiry_datetime; + bool is_expiry_datetime_valid = parse_datetime(expiry_date, &expiry_datetime); + + FuriHalRtcDateTime valid_to_datetime; + bool is_valid_to_datetime_valid = parse_datetime(valid_to, &valid_to_datetime); + + FuriHalRtcDateTime last_refill_datetime; + bool is_last_refill_datetime_valid = + parse_datetime(last_refill_date, &last_refill_datetime); + + furi_string_cat_printf( + parsed_data, + "\e#Umarsh\nCard number: %lu\nRegion: %02u\nTerminal number: %lu\nRefill counter: %u\nBalance: %u.%02u RUR", + card_number, + region_number, + terminal_number, + refill_counter, + balance_rub, + balance_kop); + + if(is_expiry_datetime_valid) + furi_string_cat_printf( + parsed_data, + "\nExpires: %02u.%02u.%u", + expiry_datetime.day, + expiry_datetime.month, + expiry_datetime.year); + if(is_valid_to_datetime_valid) + furi_string_cat_printf( + parsed_data, + "\nValid to: %02u.%02u.%u", + valid_to_datetime.day, + valid_to_datetime.month, + valid_to_datetime.year); + if(is_last_refill_datetime_valid) + furi_string_cat_printf( + parsed_data, + "\nLast refill: %02u.%02u.%u", + last_refill_datetime.day, + last_refill_datetime.month, + last_refill_datetime.year); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin umarsh_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = NULL, + .read = NULL, + .parse = umarsh_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor umarsh_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &umarsh_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* umarsh_plugin_ep() { + return &umarsh_plugin_descriptor; +} diff --git a/applications/main/subghz/helpers/subghz_gps.c b/applications/main/subghz/helpers/subghz_gps.c index 88df096e1..1ba5ca7a9 100644 --- a/applications/main/subghz/helpers/subghz_gps.c +++ b/applications/main/subghz/helpers/subghz_gps.c @@ -138,9 +138,6 @@ SubGhzGPS* subghz_gps_init() { void subghz_gps_deinit(SubGhzGPS* subghz_gps) { furi_assert(subghz_gps); - furi_thread_free(subghz_gps->thread); - - free(subghz_gps); furi_hal_uart_set_irq_cb(UART_CH, NULL, NULL); if(UART_CH == FuriHalUartIdLPUART1) { @@ -148,6 +145,11 @@ void subghz_gps_deinit(SubGhzGPS* subghz_gps) { } else { furi_hal_console_enable(); } + + furi_thread_free(subghz_gps->thread); + furi_stream_buffer_free(subghz_gps->rx_stream); + + free(subghz_gps); } void subghz_gps_start(SubGhzGPS* subghz_gps) { diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 3b4ca47b9..105b3971f 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -219,7 +219,7 @@ void subghz_scene_radio_settings_on_enter(void* context) { item = variable_item_list_add( variable_item_list, - "Time In Names", + "Protocol Names", TIMESTAMP_NAMES_COUNT, subghz_scene_receiver_config_set_timestamp_file_names, subghz); diff --git a/applications/services/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c index 14f5d98c6..042afb7d5 100644 --- a/applications/services/desktop/animations/animation_manager.c +++ b/applications/services/desktop/animations/animation_manager.c @@ -387,31 +387,22 @@ static StorageAnimation* furi_record_close(RECORD_DOLPHIN); uint32_t whole_weight = 0; + // Filter valid animations bool fallback = xtreme_settings.fallback_anim; - if(StorageAnimationList_size(animation_list) == dolphin_internal_size + 1 && !fallback) { - // One ext anim and fallback disabled, dont skip current anim (current = only ext one) - avoid_animation = NULL; - } - - StorageAnimationList_it_t it; bool unlock = xtreme_settings.unlock_anims; + StorageAnimationList_it_t it; for(StorageAnimationList_it(it, animation_list); !StorageAnimationList_end_p(it);) { StorageAnimation* storage_animation = *StorageAnimationList_ref(it); const StorageAnimationManifestInfo* manifest_info = animation_storage_get_meta(storage_animation); bool valid = animation_manager_is_valid_idle_animation(manifest_info, &stats, unlock); - if(avoid_animation != NULL && strcmp(manifest_info->name, avoid_animation) == 0) { - // Avoid repeating same animation twice - valid = false; - } if(strcmp(manifest_info->name, HARDCODED_ANIMATION_NAME) == 0 && !fallback) { // Skip fallback animation valid = false; } if(valid) { - whole_weight += manifest_info->weight; StorageAnimationList_next(it); } else { animation_storage_free_storage_animation(&storage_animation); @@ -420,6 +411,28 @@ static StorageAnimation* } } + if(StorageAnimationList_size(animation_list) == 1) { + // One valid anim, dont skip current anim (current = only ext one) + avoid_animation = NULL; + } + + // Avoid repeating current animation and calculate weights + for(StorageAnimationList_it(it, animation_list); !StorageAnimationList_end_p(it);) { + StorageAnimation* storage_animation = *StorageAnimationList_ref(it); + const StorageAnimationManifestInfo* manifest_info = + animation_storage_get_meta(storage_animation); + + if(avoid_animation && strcmp(manifest_info->name, avoid_animation) == 0) { + // Avoid repeating same animation twice + animation_storage_free_storage_animation(&storage_animation); + /* remove and increase iterator */ + StorageAnimationList_remove(animation_list, it); + } else { + whole_weight += manifest_info->weight; + StorageAnimationList_next(it); + } + } + uint32_t lucky_number = furi_hal_random_get() % whole_weight; uint32_t weight = 0; diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index d1c2ba73a..aa0c3eac1 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -757,6 +757,10 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { storage_path_trim_trailing_slashes(path2); storage_process_alias(app, path1, message->data->cequivpath.thread_id, false); storage_process_alias(app, path2, message->data->cequivpath.thread_id, false); + // Comparison is done on path name, same beginning of name != same file/folder + // Check with a / suffixed to ensure same file/folder name + furi_string_cat(path1, "/"); + furi_string_cat(path2, "/"); if(message->data->cequivpath.truncate) { furi_string_left(path2, furi_string_size(path1)); } diff --git a/applications/system/hex_viewer/application.fam b/applications/system/hex_viewer/application.fam index 821eead69..013ea09e5 100644 --- a/applications/system/hex_viewer/application.fam +++ b/applications/system/hex_viewer/application.fam @@ -1,6 +1,6 @@ App( appid="hex_viewer", - name="Hex Viewer", + name="HEX Viewer", apptype=FlipperAppType.EXTERNAL, entry_point="hex_viewer_app", requires=[ @@ -8,10 +8,11 @@ App( "dialogs", ], stack_size=2 * 1024, + order=20, fap_icon="icons/hex_10px.png", - fap_category="Tools", fap_icon_assets="icons", + fap_category="Tools", fap_author="@QtRoS", - fap_version="1.1", + fap_version="2.0", fap_description="App allows to view various files as HEX.", ) diff --git a/applications/system/hex_viewer/helpers/hex_viewer_custom_event.h b/applications/system/hex_viewer/helpers/hex_viewer_custom_event.h new file mode 100644 index 000000000..f6dde56e6 --- /dev/null +++ b/applications/system/hex_viewer/helpers/hex_viewer_custom_event.h @@ -0,0 +1,62 @@ +#pragma once + +typedef enum { + HexViewerCustomEventStartscreenUp, + HexViewerCustomEventStartscreenDown, + HexViewerCustomEventStartscreenLeft, + HexViewerCustomEventStartscreenRight, + HexViewerCustomEventStartscreenOk, + HexViewerCustomEventStartscreenBack, + HexViewerCustomEventScene1Up, + HexViewerCustomEventScene1Down, + HexViewerCustomEventScene1Left, + HexViewerCustomEventScene1Right, + HexViewerCustomEventScene1Ok, + HexViewerCustomEventScene1Back, + HexViewerCustomEventScene2Up, + HexViewerCustomEventScene2Down, + HexViewerCustomEventScene2Left, + HexViewerCustomEventScene2Right, + HexViewerCustomEventScene2Ok, + HexViewerCustomEventScene2Back, +} HexViewerCustomEvent; + +enum HexViewerCustomEventType { + // Reserve first 100 events for button types and indexes, starting from 0 + HexViewerCustomEventMenuVoid, + HexViewerCustomEventMenuSelected, + HexViewerCustomEventMenuPercentEntered, +}; + +#pragma pack(push, 1) +typedef union { + uint32_t packed_value; + struct { + uint16_t type; + int16_t value; + } content; +} HexViewerCustomEventMenu; +#pragma pack(pop) + +static inline uint32_t hex_viewer_custom_menu_event_pack(uint16_t type, int16_t value) { + HexViewerCustomEventMenu event = {.content = {.type = type, .value = value}}; + return event.packed_value; +} +static inline void + hex_viewer_custom_menu_event_unpack(uint32_t packed_value, uint16_t* type, int16_t* value) { + HexViewerCustomEventMenu event = {.packed_value = packed_value}; + if(type) *type = event.content.type; + if(value) *value = event.content.value; +} + +static inline uint16_t hex_viewer_custom_menu_event_get_type(uint32_t packed_value) { + uint16_t type; + hex_viewer_custom_menu_event_unpack(packed_value, &type, NULL); + return type; +} + +static inline int16_t hex_viewer_custom_menu_event_get_value(uint32_t packed_value) { + int16_t value; + hex_viewer_custom_menu_event_unpack(packed_value, NULL, &value); + return value; +} \ No newline at end of file diff --git a/applications/system/hex_viewer/helpers/hex_viewer_haptic.c b/applications/system/hex_viewer/helpers/hex_viewer_haptic.c new file mode 100644 index 000000000..b3d230468 --- /dev/null +++ b/applications/system/hex_viewer/helpers/hex_viewer_haptic.c @@ -0,0 +1,35 @@ +#include "hex_viewer_haptic.h" +#include "../hex_viewer.h" + +void hex_viewer_play_happy_bump(void* context) { + HexViewer* app = context; + if(app->haptic != 1) { + return; + } + notification_message(app->notification, &sequence_set_vibro_on); + furi_thread_flags_wait(0, FuriFlagWaitAny, 20); + notification_message(app->notification, &sequence_reset_vibro); +} + +void hex_viewer_play_bad_bump(void* context) { + HexViewer* app = context; + if(app->haptic != 1) { + return; + } + notification_message(app->notification, &sequence_set_vibro_on); + furi_thread_flags_wait(0, FuriFlagWaitAny, 100); + notification_message(app->notification, &sequence_reset_vibro); +} + +void hex_viewer_play_long_bump(void* context) { + HexViewer* app = context; + if(app->haptic != 1) { + return; + } + for(int i = 0; i < 4; i++) { + notification_message(app->notification, &sequence_set_vibro_on); + furi_thread_flags_wait(0, FuriFlagWaitAny, 50); + notification_message(app->notification, &sequence_reset_vibro); + furi_thread_flags_wait(0, FuriFlagWaitAny, 100); + } +} diff --git a/applications/system/hex_viewer/helpers/hex_viewer_haptic.h b/applications/system/hex_viewer/helpers/hex_viewer_haptic.h new file mode 100644 index 000000000..ade33bc78 --- /dev/null +++ b/applications/system/hex_viewer/helpers/hex_viewer_haptic.h @@ -0,0 +1,7 @@ +#include + +void hex_viewer_play_happy_bump(void* context); + +void hex_viewer_play_bad_bump(void* context); + +void hex_viewer_play_long_bump(void* context); diff --git a/applications/system/hex_viewer/helpers/hex_viewer_led.c b/applications/system/hex_viewer/helpers/hex_viewer_led.c new file mode 100644 index 000000000..d52fa5622 --- /dev/null +++ b/applications/system/hex_viewer/helpers/hex_viewer_led.c @@ -0,0 +1,39 @@ +#include "hex_viewer_led.h" +#include "../hex_viewer.h" + +void hex_viewer_led_set_rgb(void* context, int red, int green, int blue) { + HexViewer* app = context; + if(app->led != 1) { + return; + } + NotificationMessage notification_led_message_1; + notification_led_message_1.type = NotificationMessageTypeLedRed; + NotificationMessage notification_led_message_2; + notification_led_message_2.type = NotificationMessageTypeLedGreen; + NotificationMessage notification_led_message_3; + notification_led_message_3.type = NotificationMessageTypeLedBlue; + + notification_led_message_1.data.led.value = red; + notification_led_message_2.data.led.value = green; + notification_led_message_3.data.led.value = blue; + const NotificationSequence notification_sequence = { + ¬ification_led_message_1, + ¬ification_led_message_2, + ¬ification_led_message_3, + &message_do_not_reset, + NULL, + }; + notification_message(app->notification, ¬ification_sequence); + furi_thread_flags_wait( + 0, FuriFlagWaitAny, 10); //Delay, prevent removal from RAM before LED value set +} + +void hex_viewer_led_reset(void* context) { + HexViewer* app = context; + notification_message(app->notification, &sequence_reset_red); + notification_message(app->notification, &sequence_reset_green); + notification_message(app->notification, &sequence_reset_blue); + + furi_thread_flags_wait( + 0, FuriFlagWaitAny, 300); //Delay, prevent removal from RAM before LED value set +} diff --git a/applications/system/hex_viewer/helpers/hex_viewer_led.h b/applications/system/hex_viewer/helpers/hex_viewer_led.h new file mode 100644 index 000000000..ba0e1cdad --- /dev/null +++ b/applications/system/hex_viewer/helpers/hex_viewer_led.h @@ -0,0 +1,5 @@ + + +void hex_viewer_led_set_rgb(void* context, int red, int green, int blue); + +void hex_viewer_led_reset(void* context); diff --git a/applications/system/hex_viewer/helpers/hex_viewer_speaker.c b/applications/system/hex_viewer/helpers/hex_viewer_speaker.c new file mode 100644 index 000000000..4ee3de8dc --- /dev/null +++ b/applications/system/hex_viewer/helpers/hex_viewer_speaker.c @@ -0,0 +1,26 @@ +#include "hex_viewer_speaker.h" +#include "../hex_viewer.h" + +#define NOTE_INPUT 587.33f + +void hex_viewer_play_input_sound(void* context) { + HexViewer* app = context; + if(app->speaker != 1) { + return; + } + float volume = 1.0f; + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { + furi_hal_speaker_start(NOTE_INPUT, volume); + } +} + +void hex_viewer_stop_all_sound(void* context) { + HexViewer* app = context; + if(app->speaker != 1) { + return; + } + if(furi_hal_speaker_is_mine()) { + furi_hal_speaker_stop(); + furi_hal_speaker_release(); + } +} diff --git a/applications/system/hex_viewer/helpers/hex_viewer_speaker.h b/applications/system/hex_viewer/helpers/hex_viewer_speaker.h new file mode 100644 index 000000000..747d79146 --- /dev/null +++ b/applications/system/hex_viewer/helpers/hex_viewer_speaker.h @@ -0,0 +1,4 @@ +#define NOTE_INPUT 587.33f + +void hex_viewer_play_input_sound(void* context); +void hex_viewer_stop_all_sound(void* context); diff --git a/applications/system/hex_viewer/helpers/hex_viewer_storage.c b/applications/system/hex_viewer/helpers/hex_viewer_storage.c new file mode 100644 index 000000000..6b4217dc2 --- /dev/null +++ b/applications/system/hex_viewer/helpers/hex_viewer_storage.c @@ -0,0 +1,171 @@ +#include "hex_viewer_storage.h" + +static Storage* hex_viewer_open_storage() { + return furi_record_open(RECORD_STORAGE); +} + +static void hex_viewer_close_storage() { + furi_record_close(RECORD_STORAGE); +} + +static void hex_viewer_close_config_file(FlipperFormat* file) { + if(file == NULL) return; + flipper_format_file_close(file); + flipper_format_free(file); +} + +void hex_viewer_save_settings(void* context) { + HexViewer* app = context; + if(app->save_settings == 0) { + return; + } + + FURI_LOG_D(TAG, "Saving Settings"); + Storage* storage = hex_viewer_open_storage(); + FlipperFormat* fff_file = flipper_format_file_alloc(storage); + + // Overwrite wont work, so delete first + if(storage_file_exists(storage, HEX_VIEWER_SETTINGS_SAVE_PATH)) { + storage_simply_remove(storage, HEX_VIEWER_SETTINGS_SAVE_PATH); + } + + // Open File, create if not exists + if(!storage_common_stat(storage, HEX_VIEWER_SETTINGS_SAVE_PATH, NULL) == FSE_OK) { + FURI_LOG_D( + TAG, "Config file %s is not found. Will create new.", HEX_VIEWER_SETTINGS_SAVE_PATH); + if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) { + FURI_LOG_D( + TAG, "Directory %s doesn't exist. Will create new.", CONFIG_FILE_DIRECTORY_PATH); + if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) { + FURI_LOG_E(TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH); + } + } + } + + if(!flipper_format_file_open_new(fff_file, HEX_VIEWER_SETTINGS_SAVE_PATH)) { + //totp_close_config_file(fff_file); + FURI_LOG_E(TAG, "Error creating new file %s", HEX_VIEWER_SETTINGS_SAVE_PATH); + hex_viewer_close_storage(); + return; + } + + // Store Settings + flipper_format_write_header_cstr( + fff_file, HEX_VIEWER_SETTINGS_HEADER, HEX_VIEWER_SETTINGS_FILE_VERSION); + flipper_format_write_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_HAPTIC, &app->haptic, 1); + flipper_format_write_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_SPEAKER, &app->speaker, 1); + flipper_format_write_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_LED, &app->led, 1); + flipper_format_write_uint32( + fff_file, HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1); + + if(!flipper_format_rewind(fff_file)) { + hex_viewer_close_config_file(fff_file); + FURI_LOG_E(TAG, "Rewind error"); + hex_viewer_close_storage(); + return; + } + + hex_viewer_close_config_file(fff_file); + hex_viewer_close_storage(); +} + +void hex_viewer_read_settings(void* context) { + HexViewer* app = context; + Storage* storage = hex_viewer_open_storage(); + FlipperFormat* fff_file = flipper_format_file_alloc(storage); + + if(storage_common_stat(storage, HEX_VIEWER_SETTINGS_SAVE_PATH, NULL) != FSE_OK) { + hex_viewer_close_config_file(fff_file); + hex_viewer_close_storage(); + return; + } + uint32_t file_version; + FuriString* temp_str = furi_string_alloc(); + + if(!flipper_format_file_open_existing(fff_file, HEX_VIEWER_SETTINGS_SAVE_PATH)) { + FURI_LOG_E(TAG, "Cannot open file %s", HEX_VIEWER_SETTINGS_SAVE_PATH); + hex_viewer_close_config_file(fff_file); + hex_viewer_close_storage(); + return; + } + + if(!flipper_format_read_header(fff_file, temp_str, &file_version)) { + FURI_LOG_E(TAG, "Missing Header Data"); + hex_viewer_close_config_file(fff_file); + hex_viewer_close_storage(); + return; + } + + if(file_version < HEX_VIEWER_SETTINGS_FILE_VERSION) { + FURI_LOG_I(TAG, "old config version, will be removed."); + hex_viewer_close_config_file(fff_file); + hex_viewer_close_storage(); + return; + } + + flipper_format_read_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_HAPTIC, &app->haptic, 1); + flipper_format_read_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_SPEAKER, &app->speaker, 1); + flipper_format_read_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_LED, &app->led, 1); + flipper_format_read_uint32( + fff_file, HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1); + + flipper_format_rewind(fff_file); + + hex_viewer_close_config_file(fff_file); + hex_viewer_close_storage(); +} + +bool hex_viewer_open_file(void* context, const char* file_path) { + HexViewer* hex_viewer = context; + furi_assert(hex_viewer); + furi_assert(file_path); + + // TODO Separate function? + if(hex_viewer->model->stream) { + buffered_file_stream_close(hex_viewer->model->stream); + stream_free(hex_viewer->model->stream); // TODO Check + hex_viewer->model->file_offset = 0; + } + + hex_viewer->model->stream = buffered_file_stream_alloc(hex_viewer->storage); + bool isOk = true; + + do { + if(!buffered_file_stream_open( + hex_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Unable to open stream: %s", file_path); + isOk = false; + break; + }; + + hex_viewer->model->file_size = stream_size(hex_viewer->model->stream); + } while(false); + + return isOk; +} + +bool hex_viewer_read_file(void* context) { + HexViewer* hex_viewer = context; + furi_assert(hex_viewer); + furi_assert(hex_viewer->model->stream); + furi_assert(hex_viewer->model->file_offset % HEX_VIEWER_BYTES_PER_LINE == 0); + + memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE); + bool isOk = true; + + do { + uint32_t offset = hex_viewer->model->file_offset; + if(!stream_seek(hex_viewer->model->stream, offset, true)) { + FURI_LOG_E(TAG, "Unable to seek stream"); + isOk = false; + break; + } + + hex_viewer->model->file_read_bytes = stream_read( + hex_viewer->model->stream, + (uint8_t*)hex_viewer->model->file_bytes, + HEX_VIEWER_BUF_SIZE); + } while(false); + + return isOk; +} \ No newline at end of file diff --git a/applications/system/hex_viewer/helpers/hex_viewer_storage.h b/applications/system/hex_viewer/helpers/hex_viewer_storage.h new file mode 100644 index 000000000..cd0c5277b --- /dev/null +++ b/applications/system/hex_viewer/helpers/hex_viewer_storage.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include +#include +#include "../hex_viewer.h" + +#define HEX_VIEWER_SETTINGS_FILE_VERSION 1 +#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/hex_viewer") +#define HEX_VIEWER_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/hex_viewer.conf" +#define HEX_VIEWER_SETTINGS_SAVE_PATH_TMP HEX_VIEWER_SETTINGS_SAVE_PATH ".tmp" +#define HEX_VIEWER_SETTINGS_HEADER "HexViewer Config File" +#define HEX_VIEWER_SETTINGS_KEY_HAPTIC "Haptic" +#define HEX_VIEWER_SETTINGS_KEY_LED "Led" +#define HEX_VIEWER_SETTINGS_KEY_SPEAKER "Speaker" +#define HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings" + +void hex_viewer_save_settings(void* context); +void hex_viewer_read_settings(void* context); + +bool hex_viewer_open_file(void* context, const char* file_path); +bool hex_viewer_read_file(void* context); \ No newline at end of file diff --git a/applications/system/hex_viewer/hex_viewer.c b/applications/system/hex_viewer/hex_viewer.c index b98f2d867..2b5fe68f2 100644 --- a/applications/system/hex_viewer/hex_viewer.c +++ b/applications/system/hex_viewer/hex_viewer.c @@ -1,295 +1,145 @@ -#include -#include +#include "hex_viewer.h" -#include "hex_viewer_icons.h" -#include -#include -#include -#include - -#include -#include -#include -#include - -#define TAG "HexViewer" - -#define HEX_VIEWER_APP_PATH_FOLDER STORAGE_EXT_PATH_PREFIX -#define HEX_VIEWER_APP_EXTENSION "*" - -#define HEX_VIEWER_BYTES_PER_LINE 4u -#define HEX_VIEWER_LINES_ON_SCREEN 4u -#define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_LINES_ON_SCREEN * HEX_VIEWER_BYTES_PER_LINE) - -typedef struct { - uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE]; - uint32_t file_offset; - uint32_t file_read_bytes; - uint32_t file_size; - Stream* stream; - bool mode; // Print address or content -} HexViewerModel; - -typedef struct { - HexViewerModel* model; - FuriMutex** mutex; - - FuriMessageQueue* input_queue; - - ViewPort* view_port; - Gui* gui; - Storage* storage; -} HexViewer; - -static void render_callback(Canvas* canvas, void* ctx) { - HexViewer* hex_viewer = ctx; - furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); - - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - - elements_button_left(canvas, hex_viewer->model->mode ? "Addr" : "Text"); - elements_button_right(canvas, "Info"); - - int ROW_HEIGHT = 12; - int TOP_OFFSET = 10; - int LEFT_OFFSET = 3; - - uint32_t line_count = hex_viewer->model->file_size / HEX_VIEWER_BYTES_PER_LINE; - if(hex_viewer->model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1; - uint32_t first_line_on_screen = hex_viewer->model->file_offset / HEX_VIEWER_BYTES_PER_LINE; - if(line_count > HEX_VIEWER_LINES_ON_SCREEN) { - uint8_t width = canvas_width(canvas); - elements_scrollbar_pos( - canvas, - width, - 0, - ROW_HEIGHT * HEX_VIEWER_LINES_ON_SCREEN, - first_line_on_screen, // TODO - line_count - (HEX_VIEWER_LINES_ON_SCREEN - 1)); - } - - char temp_buf[32]; - uint32_t row_iters = hex_viewer->model->file_read_bytes / HEX_VIEWER_BYTES_PER_LINE; - if(hex_viewer->model->file_read_bytes % HEX_VIEWER_BYTES_PER_LINE != 0) row_iters += 1; - - for(uint32_t i = 0; i < row_iters; ++i) { - uint32_t bytes_left_per_row = - hex_viewer->model->file_read_bytes - i * HEX_VIEWER_BYTES_PER_LINE; - bytes_left_per_row = MIN(bytes_left_per_row, HEX_VIEWER_BYTES_PER_LINE); - - if(hex_viewer->model->mode) { - memcpy(temp_buf, hex_viewer->model->file_bytes[i], bytes_left_per_row); - temp_buf[bytes_left_per_row] = '\0'; - for(uint32_t j = 0; j < bytes_left_per_row; ++j) - if(!isprint((int)temp_buf[j])) temp_buf[j] = '.'; - - canvas_set_font(canvas, FontKeyboard); - canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); - } else { - uint32_t addr = hex_viewer->model->file_offset + i * HEX_VIEWER_BYTES_PER_LINE; - snprintf(temp_buf, 32, "%04lX", addr); - - canvas_set_font(canvas, FontKeyboard); - canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); - } - - char* p = temp_buf; - for(uint32_t j = 0; j < bytes_left_per_row; ++j) - p += snprintf(p, 32, "%02X ", hex_viewer->model->file_bytes[i][j]); - - canvas_set_font(canvas, FontKeyboard); - canvas_draw_str(canvas, LEFT_OFFSET + 41, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); - } - - furi_mutex_release(hex_viewer->mutex); +bool hex_viewer_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + HexViewer* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); } -static void input_callback(InputEvent* input_event, void* ctx) { - HexViewer* hex_viewer = ctx; - if(input_event->type == InputTypeShort || input_event->type == InputTypeRepeat) { - furi_message_queue_put(hex_viewer->input_queue, input_event, 0); - } +void hex_viewer_tick_event_callback(void* context) { + furi_assert(context); + HexViewer* app = context; + scene_manager_handle_tick_event(app->scene_manager); } -static HexViewer* hex_viewer_alloc() { - HexViewer* instance = malloc(sizeof(HexViewer)); - - instance->model = malloc(sizeof(HexViewerModel)); - memset(instance->model, 0x0, sizeof(HexViewerModel)); - - instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - - instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); - - instance->view_port = view_port_alloc(); - view_port_draw_callback_set(instance->view_port, render_callback, instance); - view_port_input_callback_set(instance->view_port, input_callback, instance); - - instance->gui = furi_record_open(RECORD_GUI); - gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); - - instance->storage = furi_record_open(RECORD_STORAGE); - - return instance; +//leave app if back button pressed +bool hex_viewer_navigation_event_callback(void* context) { + furi_assert(context); + HexViewer* app = context; + return scene_manager_handle_back_event(app->scene_manager); } -static void hex_viewer_free(HexViewer* instance) { +HexViewer* hex_viewer_app_alloc() { + HexViewer* app = malloc(sizeof(HexViewer)); + + app->model = malloc(sizeof(HexViewerModel)); + memset(app->model, 0, sizeof(HexViewerModel)); + + app->gui = furi_record_open(RECORD_GUI); + app->storage = furi_record_open(RECORD_STORAGE); + app->notification = furi_record_open(RECORD_NOTIFICATION); + + //Turn backlight on, believe me this makes testing your app easier + notification_message(app->notification, &sequence_display_backlight_on); + + //Scene additions + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + + app->scene_manager = scene_manager_alloc(&hex_viewer_scene_handlers, app); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, hex_viewer_navigation_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, hex_viewer_tick_event_callback, 100); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, hex_viewer_custom_event_callback); + + app->submenu = submenu_alloc(); + app->text_input = text_input_alloc(); + + // Set defaults, in case no config loaded + app->haptic = 1; + app->speaker = 1; + app->led = 1; + app->save_settings = 1; + + // Used for File Browser + app->dialogs = furi_record_open(RECORD_DIALOGS); + app->file_path = furi_string_alloc(); + + // Load configs + hex_viewer_read_settings(app); + + view_dispatcher_add_view( + app->view_dispatcher, HexViewerViewIdMenu, submenu_get_view(app->submenu)); + + app->hex_viewer_startscreen = hex_viewer_startscreen_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + HexViewerViewIdStartscreen, + hex_viewer_startscreen_get_view(app->hex_viewer_startscreen)); + + view_dispatcher_add_view( + app->view_dispatcher, HexViewerViewIdScroll, text_input_get_view(app->text_input)); + + app->variable_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + HexViewerViewIdSettings, + variable_item_list_get_view(app->variable_item_list)); + + //End Scene Additions + + return app; +} + +void hex_viewer_app_free(HexViewer* app) { + furi_assert(app); + + if(app->model->stream) buffered_file_stream_close(app->model->stream); + + // Scene manager + scene_manager_free(app->scene_manager); + + // View Dispatcher + view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdMenu); + view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdScroll); + view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdSettings); + + submenu_free(app->submenu); + text_input_free(app->text_input); + + view_dispatcher_free(app->view_dispatcher); furi_record_close(RECORD_STORAGE); - - gui_remove_view_port(instance->gui, instance->view_port); furi_record_close(RECORD_GUI); - view_port_free(instance->view_port); - furi_message_queue_free(instance->input_queue); + app->storage = NULL; + app->gui = NULL; + app->notification = NULL; - furi_mutex_free(instance->mutex); + // Close File Browser + furi_record_close(RECORD_DIALOGS); + furi_string_free(app->file_path); - if(instance->model->stream) buffered_file_stream_close(instance->model->stream); + free(app->model); - free(instance->model); - free(instance); -} - -static bool hex_viewer_open_file(HexViewer* hex_viewer, const char* file_path) { - furi_assert(hex_viewer); - furi_assert(file_path); - - hex_viewer->model->stream = buffered_file_stream_alloc(hex_viewer->storage); - bool isOk = true; - - do { - if(!buffered_file_stream_open( - hex_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { - FURI_LOG_E(TAG, "Unable to open stream: %s", file_path); - isOk = false; - break; - }; - - hex_viewer->model->file_size = stream_size(hex_viewer->model->stream); - } while(false); - - return isOk; -} - -static bool hex_viewer_read_file(HexViewer* hex_viewer) { - furi_assert(hex_viewer); - furi_assert(hex_viewer->model->stream); - furi_assert(hex_viewer->model->file_offset % HEX_VIEWER_BYTES_PER_LINE == 0); - - memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE); - bool isOk = true; - - do { - uint32_t offset = hex_viewer->model->file_offset; - if(!stream_seek(hex_viewer->model->stream, offset, true)) { - FURI_LOG_E(TAG, "Unable to seek stream"); - isOk = false; - break; - } - - hex_viewer->model->file_read_bytes = stream_read( - hex_viewer->model->stream, - (uint8_t*)hex_viewer->model->file_bytes, - HEX_VIEWER_BUF_SIZE); - } while(false); - - return isOk; + //Remove whatever is left + free(app); } int32_t hex_viewer_app(void* p) { - HexViewer* hex_viewer = hex_viewer_alloc(); + UNUSED(p); + HexViewer* app = hex_viewer_app_alloc(); - FuriString* file_path; - file_path = furi_string_alloc(); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - do { - if(p && strlen(p)) { - furi_string_set(file_path, (const char*)p); - } else { - furi_string_set(file_path, HEX_VIEWER_APP_PATH_FOLDER); + if(p && strlen(p) && hex_viewer_open_file(app, (const char*)p)) { + hex_viewer_read_file(app); + scene_manager_next_scene(app->scene_manager, HexViewerSceneStartscreen); + } else { + scene_manager_next_scene(app->scene_manager, HexViewerSceneStartscreen); + scene_manager_next_scene(app->scene_manager, HexViewerSceneOpen); + } - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options( - &browser_options, HEX_VIEWER_APP_EXTENSION, &I_hex_10px); - browser_options.hide_ext = false; + furi_hal_power_suppress_charge_enter(); - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); + view_dispatcher_run(app->view_dispatcher); - furi_record_close(RECORD_DIALOGS); - if(!res) { - FURI_LOG_I(TAG, "No file selected"); - break; - } - } + hex_viewer_save_settings(app); - FURI_LOG_I(TAG, "File selected: %s", furi_string_get_cstr(file_path)); - - if(!hex_viewer_open_file(hex_viewer, furi_string_get_cstr(file_path))) break; - hex_viewer_read_file(hex_viewer); - - InputEvent input; - while(1) { - if(furi_message_queue_get(hex_viewer->input_queue, &input, 100) == FuriStatusOk) { - if(input.key == InputKeyBack) { - break; - } else if(input.key == InputKeyUp) { - furi_check( - furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); - if(hex_viewer->model->file_offset > 0) { - hex_viewer->model->file_offset -= HEX_VIEWER_BYTES_PER_LINE; - if(!hex_viewer_read_file(hex_viewer)) break; - } - furi_mutex_release(hex_viewer->mutex); - } else if(input.key == InputKeyDown) { - furi_check( - furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); - uint32_t last_byte_on_screen = - hex_viewer->model->file_offset + hex_viewer->model->file_read_bytes; - - if(hex_viewer->model->file_size > last_byte_on_screen) { - hex_viewer->model->file_offset += HEX_VIEWER_BYTES_PER_LINE; - if(!hex_viewer_read_file(hex_viewer)) break; - } - furi_mutex_release(hex_viewer->mutex); - } else if(input.key == InputKeyLeft) { - furi_check( - furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); - hex_viewer->model->mode = !hex_viewer->model->mode; - furi_mutex_release(hex_viewer->mutex); - } else if(input.key == InputKeyRight) { - FuriString* buffer; - buffer = furi_string_alloc(); - furi_string_printf( - buffer, - "File path: %s\nFile size: %lu (0x%lX)", - furi_string_get_cstr(file_path), - hex_viewer->model->file_size, - hex_viewer->model->file_size); - - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_header( - message, "Hex Viewer v1.1", 16, 2, AlignLeft, AlignTop); - dialog_message_set_icon(message, &I_hex_10px, 3, 2); - dialog_message_set_text( - message, furi_string_get_cstr(buffer), 3, 16, AlignLeft, AlignTop); - dialog_message_set_buttons(message, NULL, NULL, "Back"); - dialog_message_show(dialogs, message); - - furi_string_free(buffer); - dialog_message_free(message); - furi_record_close(RECORD_DIALOGS); - } - } - - view_port_update(hex_viewer->view_port); - } - } while(false); - - furi_string_free(file_path); - hex_viewer_free(hex_viewer); + furi_hal_power_suppress_charge_exit(); + hex_viewer_app_free(app); return 0; } diff --git a/applications/system/hex_viewer/hex_viewer.h b/applications/system/hex_viewer/hex_viewer.h new file mode 100644 index 000000000..d1c1adba3 --- /dev/null +++ b/applications/system/hex_viewer/hex_viewer.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scenes/hex_viewer_scene.h" +#include "views/hex_viewer_startscreen.h" +#include "helpers/hex_viewer_storage.h" + +#include +#include +#include +#include + +#define TAG "HexViewer" + +#define HEX_VIEWER_APP_PATH_FOLDER "/any" // TODO ANY_PATH +#define HEX_VIEWER_APP_EXTENSION "*" +#define HEX_VIEWER_PERCENT_INPUT 16 + +#define HEX_VIEWER_BYTES_PER_LINE 4u +#define HEX_VIEWER_LINES_ON_SCREEN 4u +#define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_LINES_ON_SCREEN * HEX_VIEWER_BYTES_PER_LINE) + +typedef struct { + uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE]; + uint32_t file_offset; + uint32_t file_read_bytes; + uint32_t file_size; + + Stream* stream; +} HexViewerModel; + +// TODO Clean +typedef struct { + HexViewerModel* model; + + Gui* gui; + Storage* storage; + NotificationApp* notification; + ViewDispatcher* view_dispatcher; + Submenu* submenu; + TextInput* text_input; + SceneManager* scene_manager; + VariableItemList* variable_item_list; + HexViewerStartscreen* hex_viewer_startscreen; + DialogsApp* dialogs; // File Browser + FuriString* file_path; // File Browser + uint32_t haptic; + uint32_t speaker; + uint32_t led; + uint32_t save_settings; + char percent_buf[HEX_VIEWER_PERCENT_INPUT]; +} HexViewer; + +typedef enum { + HexViewerViewIdStartscreen, + HexViewerViewIdMenu, + HexViewerViewIdScroll, + HexViewerViewIdSettings, +} HexViewerViewId; + +typedef enum { + HexViewerHapticOff, + HexViewerHapticOn, +} HexViewerHapticState; + +typedef enum { + HexViewerSpeakerOff, + HexViewerSpeakerOn, +} HexViewerSpeakerState; + +typedef enum { + HexViewerLedOff, + HexViewerLedOn, +} HexViewerLedState; + +typedef enum { + HexViewerSettingsOff, + HexViewerSettingsOn, +} HexViewerSettingsStoreState; diff --git a/applications/system/hex_viewer/scenes/hex_viewer_scene.c b/applications/system/hex_viewer/scenes/hex_viewer_scene.c new file mode 100644 index 000000000..385828b0d --- /dev/null +++ b/applications/system/hex_viewer/scenes/hex_viewer_scene.c @@ -0,0 +1,30 @@ +#include "hex_viewer_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const hex_viewer_on_enter_handlers[])(void*) = { +#include "hex_viewer_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 hex_viewer_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "hex_viewer_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 hex_viewer_on_exit_handlers[])(void* context) = { +#include "hex_viewer_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers hex_viewer_scene_handlers = { + .on_enter_handlers = hex_viewer_on_enter_handlers, + .on_event_handlers = hex_viewer_on_event_handlers, + .on_exit_handlers = hex_viewer_on_exit_handlers, + .scene_num = HexViewerSceneNum, +}; diff --git a/applications/system/hex_viewer/scenes/hex_viewer_scene.h b/applications/system/hex_viewer/scenes/hex_viewer_scene.h new file mode 100644 index 000000000..e1f322fca --- /dev/null +++ b/applications/system/hex_viewer/scenes/hex_viewer_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) HexViewerScene##id, +typedef enum { +#include "hex_viewer_scene_config.h" + HexViewerSceneNum, +} HexViewerScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers hex_viewer_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "hex_viewer_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 "hex_viewer_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 "hex_viewer_scene_config.h" +#undef ADD_SCENE diff --git a/applications/system/hex_viewer/scenes/hex_viewer_scene_config.h b/applications/system/hex_viewer/scenes/hex_viewer_scene_config.h new file mode 100644 index 000000000..7db382bc5 --- /dev/null +++ b/applications/system/hex_viewer/scenes/hex_viewer_scene_config.h @@ -0,0 +1,6 @@ +ADD_SCENE(hex_viewer, startscreen, Startscreen) +ADD_SCENE(hex_viewer, menu, Menu) +ADD_SCENE(hex_viewer, scroll, Scroll) +ADD_SCENE(hex_viewer, info, Info) +ADD_SCENE(hex_viewer, open, Open) +ADD_SCENE(hex_viewer, settings, Settings) \ No newline at end of file diff --git a/applications/system/hex_viewer/scenes/hex_viewer_scene_info.c b/applications/system/hex_viewer/scenes/hex_viewer_scene_info.c new file mode 100644 index 000000000..39d51b8c5 --- /dev/null +++ b/applications/system/hex_viewer/scenes/hex_viewer_scene_info.c @@ -0,0 +1,42 @@ +#include "../hex_viewer.h" + +void hex_viewer_scene_info_on_enter(void* context) { + furi_assert(context); + HexViewer* app = context; + + FuriString* buffer; + buffer = furi_string_alloc(); + furi_string_printf( + buffer, + "File path: %s\nFile size: %lu (0x%lX)", + furi_string_get_cstr(app->file_path), + app->model->file_size, + app->model->file_size); + + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Hex Viewer v2.0", 16, 2, AlignLeft, AlignTop); + dialog_message_set_icon(message, &I_hex_10px, 3, 2); + dialog_message_set_text(message, furi_string_get_cstr(buffer), 3, 16, AlignLeft, AlignTop); + dialog_message_set_buttons(message, NULL, NULL, "Back"); + dialog_message_show(app->dialogs, message); + + furi_string_free(buffer); + dialog_message_free(message); + + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, HexViewerViewIdStartscreen); +} + +bool hex_viewer_scene_info_on_event(void* context, SceneManagerEvent event) { + HexViewer* app = context; + UNUSED(app); + UNUSED(event); + bool consumed = true; + + return consumed; +} + +void hex_viewer_scene_info_on_exit(void* context) { + HexViewer* app = context; + UNUSED(app); +} diff --git a/applications/system/hex_viewer/scenes/hex_viewer_scene_menu.c b/applications/system/hex_viewer/scenes/hex_viewer_scene_menu.c new file mode 100644 index 000000000..50e0b55ba --- /dev/null +++ b/applications/system/hex_viewer/scenes/hex_viewer_scene_menu.c @@ -0,0 +1,83 @@ +#include "../hex_viewer.h" + +enum SubmenuIndex { + SubmenuIndexScroll = 10, + SubmenuIndexInfo, + SubmenuIndexOpen, + // SubmenuIndexSettings, +}; + +void hex_viewer_scene_menu_submenu_callback(void* context, uint32_t index) { + HexViewer* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void hex_viewer_scene_menu_on_enter(void* context) { + HexViewer* app = context; + + submenu_set_header(app->submenu, "Select action"); + submenu_add_item( + app->submenu, + "Open file ...", + SubmenuIndexOpen, + hex_viewer_scene_menu_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Scroll to ...", + SubmenuIndexScroll, + hex_viewer_scene_menu_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Show info ...", + SubmenuIndexInfo, + hex_viewer_scene_menu_submenu_callback, + app); + // submenu_add_item(app->submenu, "Settings", SubmenuIndexSettings, hex_viewer_scene_menu_submenu_callback, app); + + submenu_set_selected_item( + app->submenu, scene_manager_get_scene_state(app->scene_manager, HexViewerSceneMenu)); + + view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdMenu); +} + +bool hex_viewer_scene_menu_on_event(void* context, SceneManagerEvent event) { + HexViewer* app = context; + + if(event.type == SceneManagerEventTypeBack) { + //exit app + // scene_manager_stop(app->scene_manager); + // view_dispatcher_stop(app->view_dispatcher); + scene_manager_previous_scene(app->scene_manager); + return true; + } else if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexScroll) { + scene_manager_set_scene_state( + app->scene_manager, HexViewerSceneMenu, SubmenuIndexScroll); + scene_manager_next_scene(app->scene_manager, HexViewerSceneScroll); + return true; + } else if(event.event == SubmenuIndexInfo) { + scene_manager_set_scene_state( + app->scene_manager, HexViewerSceneMenu, SubmenuIndexInfo); + scene_manager_next_scene(app->scene_manager, HexViewerSceneInfo); + return true; + } else if(event.event == SubmenuIndexOpen) { + scene_manager_set_scene_state( + app->scene_manager, HexViewerSceneMenu, SubmenuIndexOpen); + scene_manager_next_scene(app->scene_manager, HexViewerSceneOpen); + // } else if (event.event == SubmenuIndexSettings) { + // scene_manager_set_scene_state( + // app->scene_manager, HexViewerSceneMenu, SubmenuIndexSettings); + // scene_manager_next_scene(app->scene_manager, HexViewerSceneSettings); + // return true; + } + } + + return false; +} + +void hex_viewer_scene_menu_on_exit(void* context) { + HexViewer* app = context; + submenu_reset(app->submenu); +} \ No newline at end of file diff --git a/applications/system/hex_viewer/scenes/hex_viewer_scene_open.c b/applications/system/hex_viewer/scenes/hex_viewer_scene_open.c new file mode 100644 index 000000000..cd9e90b7f --- /dev/null +++ b/applications/system/hex_viewer/scenes/hex_viewer_scene_open.c @@ -0,0 +1,42 @@ +#include "../hex_viewer.h" + +void hex_viewer_scene_open_on_enter(void* context) { + furi_assert(context); + HexViewer* app = context; + + FuriString* initial_path; + initial_path = furi_string_alloc(); + furi_string_set(initial_path, HEX_VIEWER_APP_PATH_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, HEX_VIEWER_APP_EXTENSION, &I_hex_10px); + browser_options.hide_ext = false; + + bool success = + dialog_file_browser_show(app->dialogs, app->file_path, initial_path, &browser_options); + furi_string_free(initial_path); + + if(success) { + success = hex_viewer_open_file(app, furi_string_get_cstr(app->file_path)); + if(success) hex_viewer_read_file(app); + } + + if(success) { + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, HexViewerViewIdStartscreen); + } else { + scene_manager_previous_scene(app->scene_manager); + } +} + +bool hex_viewer_scene_open_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + bool consumed = true; + + return consumed; +} + +void hex_viewer_scene_open_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/system/hex_viewer/scenes/hex_viewer_scene_scroll.c b/applications/system/hex_viewer/scenes/hex_viewer_scene_scroll.c new file mode 100644 index 000000000..d20e9cfcb --- /dev/null +++ b/applications/system/hex_viewer/scenes/hex_viewer_scene_scroll.c @@ -0,0 +1,61 @@ +#include "../hex_viewer.h" +#include "../helpers/hex_viewer_custom_event.h" + +void hex_viewer_scene_scroll_callback(void* context) { + HexViewer* app = (HexViewer*)context; + view_dispatcher_send_custom_event( + app->view_dispatcher, HexViewerCustomEventMenuPercentEntered); +} + +void hex_viewer_scene_scroll_on_enter(void* context) { + furi_assert(context); + HexViewer* app = context; + + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Scroll to percent (0..100)"); + text_input_set_result_callback( + text_input, + hex_viewer_scene_scroll_callback, + app, + app->percent_buf, + HEX_VIEWER_PERCENT_INPUT, + false); + + view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerSceneScroll); +} + +bool hex_viewer_scene_scroll_on_event(void* context, SceneManagerEvent event) { + HexViewer* app = (HexViewer*)context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == HexViewerCustomEventMenuPercentEntered) { + int ipercent = atoi(app->percent_buf); + ipercent = MIN(ipercent, 100); + ipercent = MAX(ipercent, 0); + float percent = ipercent / 100.0; + + uint32_t line_count = app->model->file_size / HEX_VIEWER_BYTES_PER_LINE; + if(app->model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1; + uint32_t scrollable_lines = line_count - HEX_VIEWER_LINES_ON_SCREEN; + uint32_t target_line = (uint32_t)(percent * scrollable_lines); + + uint32_t new_file_offset = target_line * HEX_VIEWER_BYTES_PER_LINE; + if(app->model->file_size > new_file_offset) { + app->model->file_offset = new_file_offset; + if(!hex_viewer_read_file(app)) new_file_offset = new_file_offset; // TODO Do smth + } + + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, HexViewerViewIdStartscreen); + + consumed = true; + } + } + return consumed; +} + +void hex_viewer_scene_scroll_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/system/hex_viewer/scenes/hex_viewer_scene_settings.c b/applications/system/hex_viewer/scenes/hex_viewer_scene_settings.c new file mode 100644 index 000000000..70a8b3a41 --- /dev/null +++ b/applications/system/hex_viewer/scenes/hex_viewer_scene_settings.c @@ -0,0 +1,133 @@ +#include "../hex_viewer.h" +#include + +enum SettingsIndex { + SettingsIndexHaptic = 10, + SettingsIndexValue1, + SettingsIndexValue2, +}; + +const char* const haptic_text[2] = { + "OFF", + "ON", +}; +const uint32_t haptic_value[2] = { + HexViewerHapticOff, + HexViewerHapticOn, +}; + +const char* const speaker_text[2] = { + "OFF", + "ON", +}; +const uint32_t speaker_value[2] = { + HexViewerSpeakerOff, + HexViewerSpeakerOn, +}; + +const char* const led_text[2] = { + "OFF", + "ON", +}; +const uint32_t led_value[2] = { + HexViewerLedOff, + HexViewerLedOn, +}; + +const char* const settings_text[2] = { + "OFF", + "ON", +}; +const uint32_t settings_value[2] = { + HexViewerSettingsOff, + HexViewerSettingsOn, +}; + +static void hex_viewer_scene_settings_set_haptic(VariableItem* item) { + HexViewer* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, haptic_text[index]); + app->haptic = haptic_value[index]; +} + +static void hex_viewer_scene_settings_set_speaker(VariableItem* item) { + HexViewer* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, speaker_text[index]); + app->speaker = speaker_value[index]; +} + +static void hex_viewer_scene_settings_set_led(VariableItem* item) { + HexViewer* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, led_text[index]); + app->led = led_value[index]; +} + +static void hex_viewer_scene_settings_set_save_settings(VariableItem* item) { + HexViewer* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, settings_text[index]); + app->save_settings = settings_value[index]; +} + +void hex_viewer_scene_settings_submenu_callback(void* context, uint32_t index) { + HexViewer* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void hex_viewer_scene_settings_on_enter(void* context) { + HexViewer* app = context; + VariableItem* item; + uint8_t value_index; + + // Vibro on/off + item = variable_item_list_add( + app->variable_item_list, "Vibro/Haptic:", 2, hex_viewer_scene_settings_set_haptic, app); + value_index = value_index_uint32(app->haptic, haptic_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, haptic_text[value_index]); + + // Sound on/off + item = variable_item_list_add( + app->variable_item_list, "Sound:", 2, hex_viewer_scene_settings_set_speaker, app); + value_index = value_index_uint32(app->speaker, speaker_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, speaker_text[value_index]); + + // LED Effects on/off + item = variable_item_list_add( + app->variable_item_list, "LED FX:", 2, hex_viewer_scene_settings_set_led, app); + value_index = value_index_uint32(app->led, led_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, led_text[value_index]); + + // Save Settings to File + item = variable_item_list_add( + app->variable_item_list, + "Save Settings", + 2, + hex_viewer_scene_settings_set_save_settings, + app); + value_index = value_index_uint32(app->save_settings, settings_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, settings_text[value_index]); + + view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdSettings); +} + +bool hex_viewer_scene_settings_on_event(void* context, SceneManagerEvent event) { + HexViewer* app = context; + UNUSED(app); + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + } + return consumed; +} + +void hex_viewer_scene_settings_on_exit(void* context) { + HexViewer* app = context; + variable_item_list_set_selected_item(app->variable_item_list, 0); + variable_item_list_reset(app->variable_item_list); +} \ No newline at end of file diff --git a/applications/system/hex_viewer/scenes/hex_viewer_scene_startscreen.c b/applications/system/hex_viewer/scenes/hex_viewer_scene_startscreen.c new file mode 100644 index 000000000..6793655af --- /dev/null +++ b/applications/system/hex_viewer/scenes/hex_viewer_scene_startscreen.c @@ -0,0 +1,65 @@ +#include "../hex_viewer.h" +#include "../helpers/hex_viewer_custom_event.h" +#include "../views/hex_viewer_startscreen.h" + +void hex_viewer_scene_startscreen_callback(HexViewerCustomEvent event, void* context) { + furi_assert(context); + HexViewer* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void hex_viewer_scene_startscreen_on_enter(void* context) { + furi_assert(context); + HexViewer* app = context; + hex_viewer_startscreen_set_callback( + app->hex_viewer_startscreen, hex_viewer_scene_startscreen_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdStartscreen); +} + +bool hex_viewer_scene_startscreen_on_event(void* context, SceneManagerEvent event) { + HexViewer* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case HexViewerCustomEventStartscreenLeft: + //app->model->mode = !app->model->mode; + consumed = true; + break; + case HexViewerCustomEventStartscreenRight: + consumed = true; + break; + case HexViewerCustomEventStartscreenUp: + consumed = true; + break; + case HexViewerCustomEventStartscreenDown: + consumed = true; + break; + case HexViewerCustomEventStartscreenOk: + if(!app->model->file_size) + scene_manager_next_scene(app->scene_manager, HexViewerSceneOpen); + else + scene_manager_next_scene(app->scene_manager, HexViewerSceneMenu); + consumed = true; + break; + case HexViewerCustomEventStartscreenBack: // TODO Delete + notification_message(app->notification, &sequence_reset_red); + notification_message(app->notification, &sequence_reset_green); + notification_message(app->notification, &sequence_reset_blue); + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, HexViewerSceneStartscreen)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + consumed = true; + break; + } + } + + return consumed; +} + +void hex_viewer_scene_startscreen_on_exit(void* context) { + HexViewer* app = context; + UNUSED(app); +} \ No newline at end of file diff --git a/applications/system/hex_viewer/views/hex_viewer_startscreen.c b/applications/system/hex_viewer/views/hex_viewer_startscreen.c new file mode 100644 index 000000000..ed40de8a7 --- /dev/null +++ b/applications/system/hex_viewer/views/hex_viewer_startscreen.c @@ -0,0 +1,245 @@ +#include "../hex_viewer.h" +#include +#include +#include +#include + +struct HexViewerStartscreen { + View* view; + HexViewerStartscreenCallback callback; + void* context; +}; + +typedef struct { + uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE]; + uint32_t file_offset; + uint32_t file_read_bytes; + uint32_t file_size; + bool mode; + uint32_t dbg; +} HexViewerStartscreenModel; + +void hex_viewer_startscreen_set_callback( + HexViewerStartscreen* instance, + HexViewerStartscreenCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + instance->callback = callback; + instance->context = context; +} + +void hex_viewer_startscreen_draw(Canvas* canvas, HexViewerStartscreenModel* model) { + canvas_clear(canvas); + + if(!model->file_size) { + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "HexViewer v2.0"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignTop, "Basic hex viewer"); + canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "for your Flipper"); + elements_button_center(canvas, "Open"); + } else { + canvas_set_color(canvas, ColorBlack); + + elements_button_left(canvas, model->mode ? "Addr" : "Text"); + //elements_button_right(canvas, "Info"); + elements_button_center(canvas, "Menu"); + + int ROW_HEIGHT = 12; + int TOP_OFFSET = 10; + int LEFT_OFFSET = 3; + + uint32_t line_count = model->file_size / HEX_VIEWER_BYTES_PER_LINE; + if(model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1; + uint32_t first_line_on_screen = model->file_offset / HEX_VIEWER_BYTES_PER_LINE; + if(line_count > HEX_VIEWER_LINES_ON_SCREEN) { + uint8_t width = canvas_width(canvas); + elements_scrollbar_pos( + canvas, + width, + 0, + ROW_HEIGHT * HEX_VIEWER_LINES_ON_SCREEN, + first_line_on_screen, // TODO + line_count - (HEX_VIEWER_LINES_ON_SCREEN - 1)); + } + + char temp_buf[32]; + uint32_t row_iters = model->file_read_bytes / HEX_VIEWER_BYTES_PER_LINE; + if(model->file_read_bytes % HEX_VIEWER_BYTES_PER_LINE != 0) row_iters += 1; + + for(uint32_t i = 0; i < row_iters; ++i) { + uint32_t bytes_left_per_row = model->file_read_bytes - i * HEX_VIEWER_BYTES_PER_LINE; + bytes_left_per_row = MIN(bytes_left_per_row, HEX_VIEWER_BYTES_PER_LINE); + + if(model->mode) { + memcpy(temp_buf, model->file_bytes[i], bytes_left_per_row); + temp_buf[bytes_left_per_row] = '\0'; + for(uint32_t j = 0; j < bytes_left_per_row; ++j) + if(!isprint((int)temp_buf[j])) temp_buf[j] = '.'; + + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); + } else { + uint32_t addr = model->file_offset + i * HEX_VIEWER_BYTES_PER_LINE; + snprintf(temp_buf, 32, "%04lX", addr); + + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); + } + + char* p = temp_buf; + for(uint32_t j = 0; j < bytes_left_per_row; ++j) + p += snprintf(p, 32, "%02X ", model->file_bytes[i][j]); + + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, LEFT_OFFSET + 41, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); + } + + // Poor man's debug + // snprintf(temp_buf, 32, "D %02lX", model->dbg); + // elements_button_right(canvas, temp_buf); + } +} + +static void hex_viewer_startscreen_model_init(HexViewerStartscreenModel* const model) { + memset(model->file_bytes, 0, sizeof(model->file_bytes)); + model->file_offset = 0; + model->file_read_bytes = 0; + model->file_size = 0; + model->mode = false; + model->dbg = 0; +} + +static void + update_local_model_from_app(HexViewer* const app, HexViewerStartscreenModel* const model) { + memcpy(model->file_bytes, app->model->file_bytes, sizeof(model->file_bytes)); + model->file_offset = app->model->file_offset; + model->file_read_bytes = app->model->file_read_bytes; + model->file_size = app->model->file_size; + //model->mode = app->model->mode; +} + +bool hex_viewer_startscreen_input(InputEvent* event, void* context) { + furi_assert(context); + HexViewerStartscreen* instance = context; + HexViewer* app = instance->context; // TO so good, but works + // TODO InputTypeShort? + if(event->type == InputTypeRelease || event->type == InputTypeRepeat) { + switch(event->key) { + case InputKeyBack: + with_view_model( + instance->view, + HexViewerStartscreenModel * model, + { + instance->callback(HexViewerCustomEventStartscreenBack, instance->context); + update_local_model_from_app(instance->context, model); + }, + true); + break; + case InputKeyLeft: + with_view_model( + instance->view, + HexViewerStartscreenModel * model, + { model->mode = !model->mode; }, + true); + break; + case InputKeyRight: + with_view_model( + instance->view, HexViewerStartscreenModel * model, { model->dbg = 0; }, true); + break; + case InputKeyUp: + with_view_model( + instance->view, + HexViewerStartscreenModel * model, + { + if(app->model->file_offset > 0) { + app->model->file_offset -= HEX_VIEWER_BYTES_PER_LINE; + if(!hex_viewer_read_file(app)) break; // TODO Do smth + } + + update_local_model_from_app(instance->context, model); + }, + true); + break; + case InputKeyDown: + with_view_model( + instance->view, + HexViewerStartscreenModel * model, + { + uint32_t last_byte_on_screen = + app->model->file_offset + app->model->file_read_bytes; + if(app->model->file_size > last_byte_on_screen) { + app->model->file_offset += HEX_VIEWER_BYTES_PER_LINE; + if(!hex_viewer_read_file(app)) break; // TODO Do smth + } + + update_local_model_from_app(instance->context, model); + }, + true); + break; + case InputKeyOk: + with_view_model( + instance->view, + HexViewerStartscreenModel * model, + { + instance->callback(HexViewerCustomEventStartscreenOk, instance->context); + update_local_model_from_app(instance->context, model); + }, + true); + break; + case InputKeyMAX: + break; + } + } + + return true; +} + +void hex_viewer_startscreen_exit(void* context) { + furi_assert(context); +} + +void hex_viewer_startscreen_enter(void* context) { + furi_assert(context); + HexViewerStartscreen* instance = (HexViewerStartscreen*)context; + with_view_model( + instance->view, + HexViewerStartscreenModel * model, + { update_local_model_from_app(instance->context, model); }, + true); +} + +HexViewerStartscreen* hex_viewer_startscreen_alloc() { + HexViewerStartscreen* instance = malloc(sizeof(HexViewerStartscreen)); + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(HexViewerStartscreenModel)); + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, (ViewDrawCallback)hex_viewer_startscreen_draw); + view_set_input_callback(instance->view, hex_viewer_startscreen_input); + view_set_enter_callback(instance->view, hex_viewer_startscreen_enter); + view_set_exit_callback(instance->view, hex_viewer_startscreen_exit); + + with_view_model( + instance->view, + HexViewerStartscreenModel * model, + { hex_viewer_startscreen_model_init(model); }, + true); + + return instance; +} + +void hex_viewer_startscreen_free(HexViewerStartscreen* instance) { + furi_assert(instance); + + with_view_model( + instance->view, HexViewerStartscreenModel * model, { UNUSED(model); }, true); + view_free(instance->view); + free(instance); +} + +View* hex_viewer_startscreen_get_view(HexViewerStartscreen* instance) { + furi_assert(instance); + return instance->view; +} diff --git a/applications/system/hex_viewer/views/hex_viewer_startscreen.h b/applications/system/hex_viewer/views/hex_viewer_startscreen.h new file mode 100644 index 000000000..3bda82744 --- /dev/null +++ b/applications/system/hex_viewer/views/hex_viewer_startscreen.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include "../helpers/hex_viewer_custom_event.h" + +typedef struct HexViewerStartscreen HexViewerStartscreen; + +typedef void (*HexViewerStartscreenCallback)(HexViewerCustomEvent event, void* context); + +void hex_viewer_startscreen_set_callback( + HexViewerStartscreen* hex_viewer_startscreen, + HexViewerStartscreenCallback callback, + void* context); + +View* hex_viewer_startscreen_get_view(HexViewerStartscreen* hex_viewer_static); + +HexViewerStartscreen* hex_viewer_startscreen_alloc(); + +void hex_viewer_startscreen_free(HexViewerStartscreen* hex_viewer_static); \ No newline at end of file diff --git a/applications/system/hid_app/assets/BtnFrameLeft_3x18.png b/applications/system/hid_app/assets/BtnFrameLeft_3x18.png new file mode 100644 index 000000000..f39e89f8b Binary files /dev/null and b/applications/system/hid_app/assets/BtnFrameLeft_3x18.png differ diff --git a/applications/system/hid_app/assets/BtnFrameRight_2x18.png b/applications/system/hid_app/assets/BtnFrameRight_2x18.png new file mode 100644 index 000000000..d6edbb713 Binary files /dev/null and b/applications/system/hid_app/assets/BtnFrameRight_2x18.png differ diff --git a/applications/system/hid_app/assets/Mic_btn_8x10.png b/applications/system/hid_app/assets/Mic_btn_8x10.png new file mode 100644 index 000000000..a08bb35dd Binary files /dev/null and b/applications/system/hid_app/assets/Mic_btn_8x10.png differ diff --git a/applications/system/hid_app/assets/Pin_back_arrow_rotated_8x10.png b/applications/system/hid_app/assets/Pin_back_arrow_rotated_8x10.png new file mode 100644 index 000000000..929992022 Binary files /dev/null and b/applications/system/hid_app/assets/Pin_back_arrow_rotated_8x10.png differ diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index 4ab10c907..4792211cb 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -11,10 +11,12 @@ enum HidDebugSubmenuIndex { HidSubmenuIndexKeyboard, HidSubmenuIndexNumpad, HidSubmenuIndexMedia, + HidSubmenuIndexMovie, HidSubmenuIndexTikTok, HidSubmenuIndexMouse, HidSubmenuIndexMouseClicker, HidSubmenuIndexMouseJiggler, + HidSubmenuIndexPtt, }; static void hid_submenu_callback(void* context, uint32_t index) { @@ -37,6 +39,9 @@ static void hid_submenu_callback(void* context, uint32_t index) { } else if(index == HidSubmenuIndexMedia) { app->view_id = HidViewMedia; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia); + } else if(index == HidSubmenuIndexMovie) { + app->view_id = HidViewMovie; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMovie); } else if(index == HidSubmenuIndexMouse) { app->view_id = HidViewMouse; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse); @@ -49,6 +54,9 @@ static void hid_submenu_callback(void* context, uint32_t index) { } else if(index == HidSubmenuIndexMouseJiggler) { app->view_id = HidViewMouseJiggler; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); + } else if(index == HidSubmenuIndexPtt) { + app->view_id = HidViewPtt; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPtt); } } @@ -67,27 +75,17 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con hid_keyboard_set_connected_status(hid->hid_keyboard, connected); hid_numpad_set_connected_status(hid->hid_numpad, connected); hid_media_set_connected_status(hid->hid_media, connected); + hid_movie_set_connected_status(hid->hid_movie, connected); hid_mouse_set_connected_status(hid->hid_mouse, connected); hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected); hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected); + hid_ptt_set_connected_status(hid->hid_ptt, connected); hid_tiktok_set_connected_status(hid->hid_tiktok, connected); } -static void hid_dialog_callback(DialogExResult result, void* context) { - furi_assert(context); - Hid* app = context; - if(result == DialogExResultLeft) { - view_dispatcher_stop(app->view_dispatcher); - } else if(result == DialogExResultRight) { - view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view - } else if(result == DialogExResultCenter) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); - } -} - -static uint32_t hid_exit_confirm_view(void* context) { +static uint32_t hid_menu_view(void* context) { UNUSED(context); - return HidViewExitConfirm; + return HidViewSubmenu; } static uint32_t hid_exit(void* context) { @@ -128,6 +126,8 @@ Hid* hid_alloc(HidTransport transport) { app->device_type_submenu, "Numpad", HidSubmenuIndexNumpad, hid_submenu_callback, app); submenu_add_item( app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, "Movie", HidSubmenuIndexMovie, hid_submenu_callback, app); submenu_add_item( app->device_type_submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app); if(app->transport == HidTransportBle) { @@ -150,6 +150,8 @@ Hid* hid_alloc(HidTransport transport) { HidSubmenuIndexMouseJiggler, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, "PTT", HidSubmenuIndexPtt, hid_submenu_callback, app); view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); view_dispatcher_add_view( app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); @@ -162,56 +164,52 @@ Hid* hid_app_alloc_view(void* context) { furi_assert(context); Hid* app = context; // Dialog view - app->dialog = dialog_ex_alloc(); - dialog_ex_set_result_callback(app->dialog, hid_dialog_callback); - dialog_ex_set_context(app->dialog, app); - dialog_ex_set_left_button_text(app->dialog, "Exit"); - dialog_ex_set_right_button_text(app->dialog, "Stay"); - dialog_ex_set_center_button_text(app->dialog, "Menu"); - dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); - view_dispatcher_add_view( - app->view_dispatcher, HidViewExitConfirm, dialog_ex_get_view(app->dialog)); // Keynote view app->hid_keynote = hid_keynote_alloc(app); - view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_exit_confirm_view); + view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote)); // Keyboard view app->hid_keyboard = hid_keyboard_alloc(app); - view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view); + view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard)); //Numpad keyboard view app->hid_numpad = hid_numpad_alloc(app); - view_set_previous_callback(hid_numpad_get_view(app->hid_numpad), hid_exit_confirm_view); + view_set_previous_callback(hid_numpad_get_view(app->hid_numpad), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewNumpad, hid_numpad_get_view(app->hid_numpad)); // Media view app->hid_media = hid_media_alloc(app); - view_set_previous_callback(hid_media_get_view(app->hid_media), hid_exit_confirm_view); + view_set_previous_callback(hid_media_get_view(app->hid_media), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media)); + // Movie view + app->hid_movie = hid_movie_alloc(app); + view_set_previous_callback(hid_movie_get_view(app->hid_movie), hid_menu_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewMovie, hid_movie_get_view(app->hid_movie)); + // TikTok view app->hid_tiktok = hid_tiktok_alloc(app); - view_set_previous_callback(hid_tiktok_get_view(app->hid_tiktok), hid_exit_confirm_view); + view_set_previous_callback(hid_tiktok_get_view(app->hid_tiktok), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, BtHidViewTikTok, hid_tiktok_get_view(app->hid_tiktok)); // Mouse view app->hid_mouse = hid_mouse_alloc(app); - view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_exit_confirm_view); + view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse)); // Mouse clicker view app->hid_mouse_clicker = hid_mouse_clicker_alloc(app); - view_set_previous_callback( - hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_exit_confirm_view); + view_set_previous_callback(hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouseClicker, @@ -219,13 +217,17 @@ Hid* hid_app_alloc_view(void* context) { // Mouse jiggler view app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app); - view_set_previous_callback( - hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_exit_confirm_view); + view_set_previous_callback(hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouseJiggler, hid_mouse_jiggler_get_view(app->hid_mouse_jiggler)); + // Ptt view + app->hid_ptt = hid_ptt_alloc(app); + view_set_previous_callback(hid_ptt_get_view(app->hid_ptt), hid_menu_view); + view_dispatcher_add_view(app->view_dispatcher, HidViewPtt, hid_ptt_get_view(app->hid_ptt)); + return app; } @@ -240,8 +242,6 @@ void hid_free(Hid* app) { // Free views view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); submenu_free(app->device_type_submenu); - view_dispatcher_remove_view(app->view_dispatcher, HidViewExitConfirm); - dialog_ex_free(app->dialog); view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote); hid_keynote_free(app->hid_keynote); view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard); @@ -250,12 +250,16 @@ void hid_free(Hid* app) { hid_numpad_free(app->hid_numpad); view_dispatcher_remove_view(app->view_dispatcher, HidViewMedia); hid_media_free(app->hid_media); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMovie); + hid_movie_free(app->hid_movie); view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse); hid_mouse_free(app->hid_mouse); view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseClicker); hid_mouse_clicker_free(app->hid_mouse_clicker); view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler); hid_mouse_jiggler_free(app->hid_mouse_jiggler); + view_dispatcher_remove_view(app->view_dispatcher, HidViewPtt); + hid_ptt_free(app->hid_ptt); view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); hid_tiktok_free(app->hid_tiktok); view_dispatcher_free(app->view_dispatcher); @@ -437,9 +441,7 @@ int32_t hid_ble_app(void* p) { furi_record_close(RECORD_STORAGE); - if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { - FURI_LOG_E(TAG, "Failed to switch to HID profile"); - } + furi_check(bt_set_profile(app->bt, BtProfileHidKeyboard)); furi_hal_bt_start_advertising(); bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); @@ -457,9 +459,7 @@ int32_t hid_ble_app(void* p) { bt_keys_storage_set_default_path(app->bt); - if(!bt_set_profile(app->bt, BtProfileSerial)) { - FURI_LOG_E(TAG, "Failed to switch to Serial profile"); - } + furi_check(bt_set_profile(app->bt, BtProfileSerial)); hid_free(app); diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h index 6b32f2167..2eb224bd5 100644 --- a/applications/system/hid_app/hid.h +++ b/applications/system/hid_app/hid.h @@ -14,16 +14,19 @@ #include #include -#include #include #include "views/hid_keynote.h" #include "views/hid_keyboard.h" #include "views/hid_numpad.h" #include "views/hid_media.h" +#include "views/hid_movie.h" #include "views/hid_mouse.h" #include "views/hid_mouse_clicker.h" #include "views/hid_mouse_jiggler.h" #include "views/hid_tiktok.h" +#include "views/hid_ptt.h" + +#include #define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" @@ -40,15 +43,16 @@ struct Hid { NotificationApp* notifications; ViewDispatcher* view_dispatcher; Submenu* device_type_submenu; - DialogEx* dialog; HidKeynote* hid_keynote; HidKeyboard* hid_keyboard; HidNumpad* hid_numpad; HidMedia* hid_media; + HidMovie* hid_movie; HidMouse* hid_mouse; HidMouseClicker* hid_mouse_clicker; HidMouseJiggler* hid_mouse_jiggler; HidTikTok* hid_tiktok; + HidPtt* hid_ptt; HidTransport transport; uint32_t view_id; @@ -66,4 +70,4 @@ void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy); void hid_hal_mouse_scroll(Hid* instance, int8_t delta); void hid_hal_mouse_press(Hid* instance, uint16_t event); void hid_hal_mouse_release(Hid* instance, uint16_t event); -void hid_hal_mouse_release_all(Hid* instance); \ No newline at end of file +void hid_hal_mouse_release_all(Hid* instance); diff --git a/applications/system/hid_app/hid_usb_10px.png b/applications/system/hid_app/hid_usb_10px.png index 415de7d23..7649138eb 100644 Binary files a/applications/system/hid_app/hid_usb_10px.png and b/applications/system/hid_app/hid_usb_10px.png differ diff --git a/applications/system/hid_app/views.h b/applications/system/hid_app/views.h index 71ced9369..94279b9c4 100644 --- a/applications/system/hid_app/views.h +++ b/applications/system/hid_app/views.h @@ -4,9 +4,10 @@ typedef enum { HidViewKeyboard, HidViewNumpad, HidViewMedia, + HidViewMovie, HidViewMouse, HidViewMouseClicker, HidViewMouseJiggler, BtHidViewTikTok, - HidViewExitConfirm, -} HidView; \ No newline at end of file + HidViewPtt, +} HidView; diff --git a/applications/system/hid_app/views/hid_keyboard.c b/applications/system/hid_app/views/hid_keyboard.c index 34e34f9e7..1ce0285b2 100644 --- a/applications/system/hid_app/views/hid_keyboard.c +++ b/applications/system/hid_app/views/hid_keyboard.c @@ -5,8 +5,6 @@ #include "../hid.h" #include "hid_icons.h" -#include - #define TAG "HidKeyboard" struct HidKeyboard { diff --git a/applications/system/hid_app/views/hid_keynote.c b/applications/system/hid_app/views/hid_keynote.c index c9af01de1..7d0e125d7 100644 --- a/applications/system/hid_app/views/hid_keynote.c +++ b/applications/system/hid_app/views/hid_keynote.c @@ -4,8 +4,6 @@ #include "hid_icons.h" -#include - #define TAG "HidKeynote" struct HidKeynote { @@ -118,16 +116,16 @@ static void hid_keynote_draw_vertical_callback(Canvas* canvas, void* context) { HidKeynoteModel* model = context; // Header + canvas_set_font(canvas, FontPrimary); if(model->transport == HidTransportBle) { if(model->connected) { canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); } else { canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } - canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Keynote"); } else { - canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote"); } diff --git a/applications/system/hid_app/views/hid_media.c b/applications/system/hid_app/views/hid_media.c index 646f2849b..2fc0cfb1a 100644 --- a/applications/system/hid_app/views/hid_media.c +++ b/applications/system/hid_app/views/hid_media.c @@ -7,8 +7,6 @@ #include "hid_icons.h" -#include - #define TAG "HidMedia" struct HidMedia { @@ -87,8 +85,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { canvas_set_bitmap_mode(canvas, 0); canvas_set_color(canvas, ColorWhite); } - hid_media_draw_arrow(canvas, 65, 28, CanvasDirectionRightToLeft); + hid_media_draw_arrow(canvas, 67, 28, CanvasDirectionRightToLeft); hid_media_draw_arrow(canvas, 70, 28, CanvasDirectionRightToLeft); + canvas_draw_line(canvas, 64, 26, 64, 30); canvas_set_color(canvas, ColorBlack); // Right @@ -99,7 +98,8 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { canvas_set_color(canvas, ColorWhite); } hid_media_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight); - hid_media_draw_arrow(canvas, 101, 28, CanvasDirectionLeftToRight); + hid_media_draw_arrow(canvas, 99, 28, CanvasDirectionLeftToRight); + canvas_draw_line(canvas, 102, 26, 102, 30); canvas_set_color(canvas, ColorBlack); // Ok diff --git a/applications/system/hid_app/views/hid_mouse.c b/applications/system/hid_app/views/hid_mouse.c index 5e2dab476..3ae7c8145 100644 --- a/applications/system/hid_app/views/hid_mouse.c +++ b/applications/system/hid_app/views/hid_mouse.c @@ -4,8 +4,6 @@ #include "hid_icons.h" -#include - #define TAG "HidMouse" struct HidMouse { diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c index d3b78dd54..d85affc43 100644 --- a/applications/system/hid_app/views/hid_mouse_clicker.c +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -4,8 +4,6 @@ #include "hid_icons.h" -#include - #define TAG "HidMouseClicker" #define DEFAULT_CLICK_RATE 1 #define MAXIMUM_CLICK_RATE 60 diff --git a/applications/system/hid_app/views/hid_mouse_jiggler.c b/applications/system/hid_app/views/hid_mouse_jiggler.c index 5e1d8aabd..3da969aae 100644 --- a/applications/system/hid_app/views/hid_mouse_jiggler.c +++ b/applications/system/hid_app/views/hid_mouse_jiggler.c @@ -4,8 +4,6 @@ #include "hid_icons.h" -#include - #define TAG "HidMouseJiggler" struct HidMouseJiggler { @@ -84,7 +82,7 @@ static void hid_mouse_jiggler_timer_callback(void* context) { model->counter++; hid_hal_mouse_move( hid_mouse_jiggler->hid, - (model->counter % 2 == 0) ? MOUSE_MOVE_SHORT : -MOUSE_MOVE_SHORT, + (model->counter % 2 == 0) ? MOUSE_MOVE_TINY : -MOUSE_MOVE_TINY, 0); } }, diff --git a/applications/system/hid_app/views/hid_mouse_jiggler.h b/applications/system/hid_app/views/hid_mouse_jiggler.h index 0813b4351..c1f77251e 100644 --- a/applications/system/hid_app/views/hid_mouse_jiggler.h +++ b/applications/system/hid_app/views/hid_mouse_jiggler.h @@ -2,8 +2,7 @@ #include -#define MOUSE_MOVE_SHORT 5 -#define MOUSE_MOVE_LONG 20 +#define MOUSE_MOVE_TINY 1 typedef struct Hid Hid; typedef struct HidMouseJiggler HidMouseJiggler; diff --git a/applications/system/hid_app/views/hid_movie.c b/applications/system/hid_app/views/hid_movie.c new file mode 100644 index 000000000..866a4c626 --- /dev/null +++ b/applications/system/hid_app/views/hid_movie.c @@ -0,0 +1,229 @@ +#include "hid_movie.h" +#include +#include +#include +#include +#include "../hid.h" + +#include "hid_icons.h" + +#define TAG "HidMovie" + +struct HidMovie { + View* view; + Hid* hid; +}; + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool ok_pressed; + bool connected; + bool back_pressed; + HidTransport transport; +} HidMovieModel; + +static void hid_movie_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { + canvas_draw_triangle(canvas, x, y, 5, 3, dir); + if(dir == CanvasDirectionBottomToTop) { + canvas_draw_dot(canvas, x, y - 1); + } else if(dir == CanvasDirectionTopToBottom) { + canvas_draw_dot(canvas, x, y + 1); + } else if(dir == CanvasDirectionRightToLeft) { + canvas_draw_dot(canvas, x - 1, y); + } else if(dir == CanvasDirectionLeftToRight) { + canvas_draw_dot(canvas, x + 1, y); + } +} + +static void hid_movie_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidMovieModel* model = context; + + // Header + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + } + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Movie"); + canvas_set_font(canvas, FontSecondary); + + // Keypad circles + canvas_draw_icon(canvas, 58, 3, &I_OutCircles_70x51); + + // Up + if(model->up_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6); + canvas_set_color(canvas, ColorBlack); + + // Down + if(model->down_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6); + canvas_set_color(canvas, ColorBlack); + + // Left + if(model->left_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + hid_movie_draw_arrow(canvas, 65, 28, CanvasDirectionRightToLeft); + hid_movie_draw_arrow(canvas, 70, 28, CanvasDirectionRightToLeft); + canvas_set_color(canvas, ColorBlack); + + // Right + if(model->right_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + hid_movie_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight); + hid_movie_draw_arrow(canvas, 101, 28, CanvasDirectionLeftToRight); + canvas_set_color(canvas, ColorBlack); + + // Ok + if(model->ok_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + hid_movie_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight); + canvas_draw_line(canvas, 84, 26, 84, 30); + canvas_draw_line(canvas, 86, 26, 86, 30); + canvas_set_color(canvas, ColorBlack); + + // Exit + if(model->back_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); +} + +static void hid_movie_process_press(HidMovie* hid_movie, InputEvent* event) { + with_view_model( + hid_movie->view, + HidMovieModel * model, + { + if(event->key == InputKeyUp) { + model->up_pressed = true; + hid_hal_consumer_key_press(hid_movie->hid, HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + hid_hal_consumer_key_press(hid_movie->hid, HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + hid_hal_keyboard_press(hid_movie->hid, HID_KEYBOARD_LEFT_ARROW); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + hid_hal_keyboard_press(hid_movie->hid, HID_KEYBOARD_RIGHT_ARROW); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + hid_hal_consumer_key_press(hid_movie->hid, HID_CONSUMER_PLAY_PAUSE); + } else if(event->key == InputKeyBack) { + model->back_pressed = true; + } + }, + true); +} + +static void hid_movie_process_release(HidMovie* hid_movie, InputEvent* event) { + with_view_model( + hid_movie->view, + HidMovieModel * model, + { + if(event->key == InputKeyUp) { + model->up_pressed = false; + hid_hal_consumer_key_release(hid_movie->hid, HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + hid_hal_consumer_key_release(hid_movie->hid, HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + hid_hal_keyboard_release(hid_movie->hid, HID_KEYBOARD_LEFT_ARROW); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + hid_hal_keyboard_release(hid_movie->hid, HID_KEYBOARD_RIGHT_ARROW); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + hid_hal_consumer_key_release(hid_movie->hid, HID_CONSUMER_PLAY_PAUSE); + } else if(event->key == InputKeyBack) { + model->back_pressed = false; + } + }, + true); +} + +static bool hid_movie_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidMovie* hid_movie = context; + bool consumed = false; + + if(event->type == InputTypePress) { + hid_movie_process_press(hid_movie, event); + consumed = true; + } else if(event->type == InputTypeRelease) { + hid_movie_process_release(hid_movie, event); + consumed = true; + } + return consumed; +} + +HidMovie* hid_movie_alloc(Hid* hid) { + HidMovie* hid_movie = malloc(sizeof(HidMovie)); + hid_movie->view = view_alloc(); + hid_movie->hid = hid; + view_set_context(hid_movie->view, hid_movie); + view_allocate_model(hid_movie->view, ViewModelTypeLocking, sizeof(HidMovieModel)); + view_set_draw_callback(hid_movie->view, hid_movie_draw_callback); + view_set_input_callback(hid_movie->view, hid_movie_input_callback); + + with_view_model( + hid_movie->view, HidMovieModel * model, { model->transport = hid->transport; }, true); + + return hid_movie; +} + +void hid_movie_free(HidMovie* hid_movie) { + furi_assert(hid_movie); + view_free(hid_movie->view); + free(hid_movie); +} + +View* hid_movie_get_view(HidMovie* hid_movie) { + furi_assert(hid_movie); + return hid_movie->view; +} + +void hid_movie_set_connected_status(HidMovie* hid_movie, bool connected) { + furi_assert(hid_movie); + with_view_model( + hid_movie->view, HidMovieModel * model, { model->connected = connected; }, true); +} diff --git a/applications/system/hid_app/views/hid_movie.h b/applications/system/hid_app/views/hid_movie.h new file mode 100644 index 000000000..52dedc988 --- /dev/null +++ b/applications/system/hid_app/views/hid_movie.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidMovie HidMovie; + +HidMovie* hid_movie_alloc(Hid* bt_hid); + +void hid_movie_free(HidMovie* hid_movie); + +View* hid_movie_get_view(HidMovie* hid_movie); + +void hid_movie_set_connected_status(HidMovie* hid_movie, bool connected); diff --git a/applications/system/hid_app/views/hid_numpad.c b/applications/system/hid_app/views/hid_numpad.c index 4829f3c4b..bd4788b83 100644 --- a/applications/system/hid_app/views/hid_numpad.c +++ b/applications/system/hid_app/views/hid_numpad.c @@ -5,8 +5,6 @@ #include "../hid.h" #include "hid_icons.h" -#include - #define TAG "HidNumpad" struct HidNumpad { diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c new file mode 100644 index 000000000..3670cb219 --- /dev/null +++ b/applications/system/hid_app/views/hid_ptt.c @@ -0,0 +1,461 @@ +#include "hid_ptt.h" +#include +#include +#include "../hid.h" +#include "../views.h" + +#include "hid_icons.h" + +#define TAG "HidPtt" + +struct HidPtt { + View* view; + Hid* hid; +}; + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool muted; + bool ptt_pressed; + bool mic_pressed; + bool connected; + bool is_mac_os; + uint32_t appIndex; + HidTransport transport; +} HidPttModel; + +enum HidPttAppIndex { + HidPttAppIndexGoogleMeet, + HidPttAppIndexZoom, + HidPttAppIndexFaceTime, + HidPttAppIndexSkype, + HidPttAppIndexSize, +}; + +static void hid_ptt_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidPttModel* model = context; + + // Header + canvas_set_font(canvas, FontPrimary); + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + } + + // App selection + const uint8_t y_app = 78; + canvas_set_font(canvas, FontSecondary); + canvas_draw_icon(canvas, 0, y_app, &I_ButtonLeft_4x7); + if(model->appIndex == HidPttAppIndexGoogleMeet) { + elements_multiline_text_aligned(canvas, 7, y_app, AlignLeft, AlignTop, "Google Meet"); + } else if(model->appIndex == HidPttAppIndexZoom) { + elements_multiline_text_aligned(canvas, 7, y_app, AlignLeft, AlignTop, "Zoom"); + } else if(model->appIndex == HidPttAppIndexFaceTime) { + elements_multiline_text_aligned(canvas, 7, y_app, AlignLeft, AlignTop, "FaceTime"); + } else if(model->appIndex == HidPttAppIndexSkype) { + elements_multiline_text_aligned(canvas, 7, y_app, AlignLeft, AlignTop, "Skype"); + } + canvas_draw_icon(canvas, 60, y_app, &I_ButtonRight_4x7); + + // OS selection + const uint8_t y_os = 88; + const uint8_t x_os = 7; + // elements_slightly_rounded_box(canvas, model->is_mac_os ? 0 : 26, y_os, model->is_mac_os ? 21 : 26, 11); + elements_slightly_rounded_box( + canvas, model->is_mac_os ? x_os : x_os + 26, y_os, model->is_mac_os ? 21 : 26, 11); + canvas_set_color(canvas, model->is_mac_os ? ColorWhite : ColorBlack); + elements_multiline_text_aligned(canvas, x_os + 2, y_os + 1, AlignLeft, AlignTop, "Mac"); + canvas_set_color(canvas, ColorBlack); + if(model->appIndex != HidPttAppIndexFaceTime) { + elements_multiline_text_aligned(canvas, x_os + 23, y_os + 2, AlignLeft, AlignTop, "|"); + canvas_set_color(canvas, model->is_mac_os ? ColorBlack : ColorWhite); + elements_multiline_text_aligned(canvas, x_os + 28, y_os + 2, AlignLeft, AlignTop, "Linux"); + canvas_set_color(canvas, ColorBlack); + } + + // Mic label + const uint8_t y_mic = 102; + canvas_draw_icon(canvas, 19, y_mic - 1, &I_Pin_back_arrow_rotated_8x10); + elements_multiline_text_aligned(canvas, 0, y_mic, AlignLeft, AlignTop, "Hold to sync"); + elements_multiline_text_aligned(canvas, 20, y_mic + 10, AlignLeft, AlignTop, "mic status"); + + // Exit label + canvas_draw_icon(canvas, 20, 121, &I_ButtonLeft_4x7); + elements_multiline_text_aligned(canvas, 0, 121, AlignLeft, AlignTop, "Hold to exit"); + + const uint8_t x_1 = 0; + const uint8_t x_2 = x_1 + 19 + 4; + const uint8_t x_3 = x_1 + 19 * 2 + 8; + + const uint8_t y_1 = 19; + const uint8_t y_2 = y_1 + 19; + const uint8_t y_3 = y_2 + 19; + + // Up + canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18); + if(model->up_pressed) { + elements_slightly_rounded_box(canvas, x_2 + 3, y_1 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + if(model->ptt_pressed) { + if(model->appIndex != HidPttAppIndexFaceTime) { + elements_multiline_text_aligned(canvas, x_2 + 4, y_1 + 5, AlignLeft, AlignTop, "OS"); + } + } else { + canvas_draw_icon(canvas, x_2 + 5, y_1 + 5, &I_Volup_8x6); + } + canvas_set_color(canvas, ColorBlack); + + // Down + canvas_draw_icon(canvas, x_2, y_3, &I_Button_18x18); + if(model->down_pressed) { + elements_slightly_rounded_box(canvas, x_2 + 3, y_3 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + if(!model->ptt_pressed) { + canvas_draw_icon(canvas, x_2 + 6, y_3 + 5, &I_Voldwn_6x6); + } + canvas_set_color(canvas, ColorBlack); + + // Left + canvas_draw_icon(canvas, x_1, y_2, &I_Button_18x18); + if(model->left_pressed) { + elements_slightly_rounded_box(canvas, x_1 + 3, y_2 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + if(model->ptt_pressed) { + canvas_draw_icon(canvas, x_1 + 7, y_2 + 5, &I_ButtonLeft_4x7); + } else { + canvas_draw_icon(canvas, x_1 + 4, y_2 + 5, &I_Pin_back_arrow_10x8); + } + canvas_set_color(canvas, ColorBlack); + + // Right / Camera + canvas_draw_icon(canvas, x_3, y_2, &I_Button_18x18); + if(model->right_pressed) { + elements_slightly_rounded_box(canvas, x_3 + 3, y_2 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + if(!model->ptt_pressed) { + if(model->appIndex != HidPttAppIndexFaceTime) { + canvas_draw_icon(canvas, x_3 + 11, y_2 + 5, &I_ButtonLeft_4x7); + canvas_draw_box(canvas, x_3 + 4, y_2 + 5, 7, 7); + } + } else { + canvas_draw_icon(canvas, x_3 + 8, y_2 + 5, &I_ButtonRight_4x7); + } + canvas_set_color(canvas, ColorBlack); + + // Back / Mic + const uint8_t x_mic = x_3; + canvas_draw_icon(canvas, x_mic, 0, &I_Button_18x18); + if(model->mic_pressed) { + elements_slightly_rounded_box(canvas, x_mic + 3, 0 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, x_mic + 5, 0 + 4, &I_Mic_btn_8x10); + if(model->muted && !model->ptt_pressed) { + canvas_draw_line(canvas, x_mic + 3, 2, x_mic + 3 + 13, 2 + 13); + canvas_draw_line(canvas, x_mic + 2, 2, x_mic + 2 + 13, 2 + 13); + canvas_draw_line(canvas, x_mic + 3, 2 + 13, x_mic + 3 + 13, 2); + canvas_draw_line(canvas, x_mic + 2, 2 + 13, x_mic + 2 + 13, 2); + } + canvas_set_color(canvas, ColorBlack); + + // Ok / PTT + const uint8_t x_ptt_margin = 4; + const uint8_t x_ptt_width = 17; + const uint8_t x_ptt = x_1 + 19; + canvas_draw_icon(canvas, x_ptt, y_2, &I_BtnFrameLeft_3x18); + canvas_draw_icon(canvas, x_ptt + x_ptt_width + 3 + x_ptt_margin, y_2, &I_BtnFrameRight_2x18); + canvas_draw_line(canvas, x_ptt + 3, y_2, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2); + canvas_draw_line( + canvas, x_ptt + 3, y_2 + 16, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2 + 16); + canvas_draw_line( + canvas, x_ptt + 3, y_2 + 17, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2 + 17); + if(model->ptt_pressed) { + elements_slightly_rounded_box(canvas, x_ptt + 3, y_2 + 2, x_ptt_width + x_ptt_margin, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned( + canvas, x_ptt + 2 + x_ptt_margin / 2, y_2 + 13, AlignLeft, AlignBottom, "PTT"); + canvas_set_font(canvas, FontSecondary); +} + +static void hid_ptt_trigger_mute(HidPtt* hid_ptt, HidPttModel* model) { + if(model->appIndex == HidPttAppIndexGoogleMeet && model->is_mac_os) { + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D); + } else if(model->appIndex == HidPttAppIndexGoogleMeet && !model->is_mac_os) { + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D); + } else if(model->appIndex == HidPttAppIndexZoom && model->is_mac_os) { + hid_hal_keyboard_press( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); + } else if(model->appIndex == HidPttAppIndexFaceTime) { + hid_hal_keyboard_press( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + } else if(model->appIndex == HidPttAppIndexSkype && model->is_mac_os) { + hid_hal_keyboard_press( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + } else if(model->appIndex == HidPttAppIndexSkype && !model->is_mac_os) { + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M); + } +} + +static void hid_ptt_trigger_camera(HidPtt* hid_ptt, HidPttModel* model) { + if(model->appIndex == HidPttAppIndexGoogleMeet && model->is_mac_os) { + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E); + } else if(model->appIndex == HidPttAppIndexGoogleMeet && !model->is_mac_os) { + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E); + } else if(model->appIndex == HidPttAppIndexZoom && model->is_mac_os) { + hid_hal_keyboard_press( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); + } else if(model->appIndex == HidPttAppIndexZoom && !model->is_mac_os) { + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V); + } else if(model->appIndex == HidPttAppIndexSkype && model->is_mac_os) { + hid_hal_keyboard_press( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K); + } else if(model->appIndex == HidPttAppIndexSkype && !model->is_mac_os) { + hid_hal_keyboard_press( + hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K); + } +} + +static void hid_ptt_start_ptt(HidPtt* hid_ptt, HidPttModel* model) { + if(model->appIndex == HidPttAppIndexGoogleMeet) { + hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_SPACEBAR); + } else if(model->appIndex == HidPttAppIndexZoom) { + hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_SPACEBAR); + } else if(model->appIndex == HidPttAppIndexFaceTime) { + hid_hal_keyboard_press( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + } else if(model->appIndex == HidPttAppIndexSkype && model->is_mac_os) { + hid_hal_keyboard_press( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + } else if(model->appIndex == HidPttAppIndexSkype && !model->is_mac_os) { + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M); + } +} + +static void hid_ptt_stop_ptt(HidPtt* hid_ptt, HidPttModel* model) { + if(model->appIndex == HidPttAppIndexGoogleMeet) { + hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_SPACEBAR); + } else if(model->appIndex == HidPttAppIndexZoom) { + hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_SPACEBAR); + } else if(model->appIndex == HidPttAppIndexFaceTime) { + hid_hal_keyboard_press( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + } else if(model->appIndex == HidPttAppIndexSkype && model->is_mac_os) { + hid_hal_keyboard_press( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + } else if(model->appIndex == HidPttAppIndexSkype && !model->is_mac_os) { + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M); + } +} + +// Supports only ±1 +static void hid_ptt_shift_app(HidPttModel* model, int shift) { + int i = (short)model->appIndex; + if(i + shift >= HidPttAppIndexSize) { + model->appIndex = 0; + } else if(i + shift <= 0) { + model->appIndex = HidPttAppIndexSize - 1; + } else { + model->appIndex += shift; + } + // Avoid showing facetime if not macos + if(model->appIndex == HidPttAppIndexFaceTime && !model->is_mac_os) { + hid_ptt_shift_app(model, shift); + } +} + +static void hid_ptt_process(HidPtt* hid_ptt, InputEvent* event) { + with_view_model( + hid_ptt->view, + HidPttModel * model, + { + if(event->type == InputTypePress) { + if(event->key == InputKeyUp) { + model->up_pressed = true; + if(!model->ptt_pressed) { + hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_INCREMENT); + } else { + if(model->appIndex != HidPttAppIndexFaceTime) { + model->is_mac_os = !model->is_mac_os; + notification_message( + hid_ptt->hid->notifications, &sequence_single_vibro); + } + } + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + if(!model->ptt_pressed) { + hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT); + } else { + hid_ptt_shift_app(model, -1); + notification_message(hid_ptt->hid->notifications, &sequence_single_vibro); + } + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + if(model->ptt_pressed) { + hid_ptt_shift_app(model, 1); + notification_message(hid_ptt->hid->notifications, &sequence_single_vibro); + } + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + if(model->ptt_pressed) { + hid_ptt_shift_app(model, -1); + notification_message(hid_ptt->hid->notifications, &sequence_single_vibro); + } + } else if(event->key == InputKeyOk) { + model->ptt_pressed = true; + if(model->muted) { + hid_ptt_start_ptt(hid_ptt, model); + } + } else if(event->key == InputKeyBack) { + model->mic_pressed = true; + } + } else if(event->type == InputTypeRelease) { + if(event->key == InputKeyUp) { + model->up_pressed = false; + if(!model->ptt_pressed) { + hid_hal_consumer_key_release(hid_ptt->hid, HID_CONSUMER_VOLUME_INCREMENT); + } + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + if(!model->ptt_pressed) { + hid_hal_consumer_key_release(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT); + } + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + + } else if(event->key == InputKeyOk) { + model->ptt_pressed = false; + if(model->muted) { + hid_ptt_stop_ptt(hid_ptt, model); + } else { + hid_ptt_trigger_mute(hid_ptt, model); + model->muted = true; + } + } else if(event->key == InputKeyBack) { + model->mic_pressed = false; + } + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyBack && + !model->ptt_pressed) { // no changes if PTT is pressed + model->muted = !model->muted; + hid_ptt_trigger_mute(hid_ptt, model); + } else if(event->key == InputKeyRight) { + if(!model->ptt_pressed) { + hid_ptt_trigger_camera(hid_ptt, model); + } + } + } else if(event->type == InputTypeLong) { + if(event->key == InputKeyLeft) { + model->left_pressed = false; + if(!model->ptt_pressed) { + hid_hal_keyboard_release_all(hid_ptt->hid); + view_dispatcher_switch_to_view( + hid_ptt->hid->view_dispatcher, HidViewSubmenu); + // sequence_double_vibro to notify that we quit PTT + notification_message(hid_ptt->hid->notifications, &sequence_double_vibro); + } + } else if(event->key == InputKeyBack && !model->ptt_pressed) { // no changes if PTT is pressed + // Change local mic status + model->muted = !model->muted; + notification_message(hid_ptt->hid->notifications, &sequence_single_vibro); + } + } + //LED + if(model->muted && !model->ptt_pressed) { + notification_message(hid_ptt->hid->notifications, &sequence_reset_red); + } else { + notification_message(hid_ptt->hid->notifications, &sequence_set_red_255); + } + }, + true); +} + +static bool hid_ptt_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidPtt* hid_ptt = context; + bool consumed = true; + hid_ptt_process(hid_ptt, event); + return consumed; +} + +HidPtt* hid_ptt_alloc(Hid* hid) { + HidPtt* hid_ptt = malloc(sizeof(HidPtt)); + hid_ptt->view = view_alloc(); + hid_ptt->hid = hid; + view_set_context(hid_ptt->view, hid_ptt); + view_allocate_model(hid_ptt->view, ViewModelTypeLocking, sizeof(HidPttModel)); + view_set_draw_callback(hid_ptt->view, hid_ptt_draw_callback); + view_set_input_callback(hid_ptt->view, hid_ptt_input_callback); + view_set_orientation(hid_ptt->view, ViewOrientationVerticalFlip); + + with_view_model( + hid_ptt->view, + HidPttModel * model, + { + model->transport = hid->transport; + model->muted = true; // assume we're muted + model->is_mac_os = true; + }, + true); + return hid_ptt; +} + +void hid_ptt_free(HidPtt* hid_ptt) { + furi_assert(hid_ptt); + notification_message(hid_ptt->hid->notifications, &sequence_reset_red); + view_free(hid_ptt->view); + free(hid_ptt); +} + +View* hid_ptt_get_view(HidPtt* hid_ptt) { + furi_assert(hid_ptt); + return hid_ptt->view; +} + +void hid_ptt_set_connected_status(HidPtt* hid_ptt, bool connected) { + furi_assert(hid_ptt); + with_view_model( + hid_ptt->view, HidPttModel * model, { model->connected = connected; }, true); +} diff --git a/applications/system/hid_app/views/hid_ptt.h b/applications/system/hid_app/views/hid_ptt.h new file mode 100644 index 000000000..eb205c20e --- /dev/null +++ b/applications/system/hid_app/views/hid_ptt.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidPtt HidPtt; + +HidPtt* hid_ptt_alloc(Hid* bt_hid); + +void hid_ptt_free(HidPtt* hid_ptt); + +View* hid_ptt_get_view(HidPtt* hid_ptt); + +void hid_ptt_set_connected_status(HidPtt* hid_ptt, bool connected); diff --git a/applications/system/hid_app/views/hid_tiktok.c b/applications/system/hid_app/views/hid_tiktok.c index 047563131..0bfcaa2f4 100644 --- a/applications/system/hid_app/views/hid_tiktok.c +++ b/applications/system/hid_app/views/hid_tiktok.c @@ -4,8 +4,6 @@ #include "hid_icons.h" -#include - #define TAG "HidTikTok" struct HidTikTok { diff --git a/applications/system/nightstand/clock_app.c b/applications/system/nightstand/clock_app.c index 787352e93..bb7e5759d 100644 --- a/applications/system/nightstand/clock_app.c +++ b/applications/system/nightstand/clock_app.c @@ -346,8 +346,8 @@ int32_t clock_app(void* p) { furi_hal_rtc_get_datetime(&plugin_state->datetime); }*/ - view_port_update(view_port); furi_mutex_release(plugin_state->mutex); + view_port_update(view_port); } furi_timer_free(timer); diff --git a/applications/system/subghz_remote/application.fam b/applications/system/subghz_remote/application.fam index 1a99a2025..2249931b9 100644 --- a/applications/system/subghz_remote/application.fam +++ b/applications/system/subghz_remote/application.fam @@ -3,8 +3,17 @@ App( name="Sub-GHz Remote", apptype=FlipperAppType.EXTERNAL, entry_point="subghz_remote_app", + requires=[ + "gui", + "dialogs", + ], stack_size=2 * 1024, + targets=["f7"], fap_icon="icon.png", + fap_author="@gid9798 and @xMasterX", fap_description="SubGhz Remote, uses up to 5 .sub files", fap_category="Sub-GHz", + fap_icon_assets="icons", + fap_version="1.3", + fap_weburl="https://github.com/DarkFlippers/SubGHz_Remote", ) diff --git a/applications/system/subghz_remote/helpers/subrem_custom_event.h b/applications/system/subghz_remote/helpers/subrem_custom_event.h index e6b9e8ac6..810df6a89 100644 --- a/applications/system/subghz_remote/helpers/subrem_custom_event.h +++ b/applications/system/subghz_remote/helpers/subrem_custom_event.h @@ -48,4 +48,11 @@ typedef enum { SubRemCustomEventSceneEditPreviewSaved, SubRemCustomEventSceneNewName, + +#ifdef FW_ORIGIN_Official + SubRemCustomEventSceneFwWarningExit, + SubRemCustomEventSceneFwWarningNext, + SubRemCustomEventSceneFwWarningContinue, +#endif + } SubRemCustomEvent; \ No newline at end of file diff --git a/applications/system/subghz_remote/helpers/subrem_presets.c b/applications/system/subghz_remote/helpers/subrem_presets.c index ca91ccbaf..bca888b96 100644 --- a/applications/system/subghz_remote/helpers/subrem_presets.c +++ b/applications/system/subghz_remote/helpers/subrem_presets.c @@ -149,7 +149,9 @@ SubRemLoadSubState subrem_sub_preset_load( if(protocol->flag & SubGhzProtocolFlag_Send) { if((protocol->type == SubGhzProtocolTypeStatic) || (protocol->type == SubGhzProtocolTypeDynamic) || +#ifndef FW_ORIGIN_Official (protocol->type == SubGhzProtocolTypeBinRAW) || +#endif (protocol->type == SubGhzProtocolTypeRAW)) { sub_preset->type = protocol->type; } else { diff --git a/applications/system/subghz_remote/helpers/txrx/subghz_txrx.c b/applications/system/subghz_remote/helpers/txrx/subghz_txrx.c index aa713c7a8..8c8c3b56d 100644 --- a/applications/system/subghz_remote/helpers/txrx/subghz_txrx.c +++ b/applications/system/subghz_remote/helpers/txrx/subghz_txrx.c @@ -1,10 +1,12 @@ #include "subghz_txrx_i.h" -#include +#include #include #include +#ifndef FW_ORIGIN_Official #include +#endif #define TAG "SubGhz" @@ -657,12 +659,14 @@ bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance) { return instance->debug_pin_state; } +#ifndef FW_ORIGIN_Official void subghz_txrx_reset_dynamic_and_custom_btns(SubGhzTxRx* instance) { furi_assert(instance); subghz_environment_reset_keeloq(instance->environment); subghz_custom_btns_reset(); } +#endif SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance) { furi_assert(instance); diff --git a/applications/system/subghz_remote/helpers/txrx/subghz_txrx.h b/applications/system/subghz_remote/helpers/txrx/subghz_txrx.h index 4593ea20c..93c4a2276 100644 --- a/applications/system/subghz_remote/helpers/txrx/subghz_txrx.h +++ b/applications/system/subghz_remote/helpers/txrx/subghz_txrx.h @@ -369,7 +369,7 @@ bool subghz_txrx_radio_device_is_tx_allowed(SubGhzTxRx* instance, uint32_t frequ void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state); bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance); - +#ifndef FW_ORIGIN_Official void subghz_txrx_reset_dynamic_and_custom_btns(SubGhzTxRx* instance); - +#endif SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance); // TODO use only in DecodeRaw diff --git a/applications/system/subghz_remote/icons/remote_scene/Dpad/down.png b/applications/system/subghz_remote/icons/remote_scene/Dpad/down.png new file mode 100644 index 000000000..198e22ea2 Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/Dpad/down.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/Dpad/down_hover.png b/applications/system/subghz_remote/icons/remote_scene/Dpad/down_hover.png new file mode 100644 index 000000000..39ac087e7 Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/Dpad/down_hover.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/Dpad/left.png b/applications/system/subghz_remote/icons/remote_scene/Dpad/left.png new file mode 100644 index 000000000..38b83c79b Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/Dpad/left.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/Dpad/left_hover.png b/applications/system/subghz_remote/icons/remote_scene/Dpad/left_hover.png new file mode 100644 index 000000000..45f58b8b6 Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/Dpad/left_hover.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/Dpad/ok.png b/applications/system/subghz_remote/icons/remote_scene/Dpad/ok.png new file mode 100644 index 000000000..dfd3d2fd1 Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/Dpad/ok.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/Dpad/ok_hover.png b/applications/system/subghz_remote/icons/remote_scene/Dpad/ok_hover.png new file mode 100644 index 000000000..9107c1a79 Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/Dpad/ok_hover.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/Dpad/right.png b/applications/system/subghz_remote/icons/remote_scene/Dpad/right.png new file mode 100644 index 000000000..ca6748f9a Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/Dpad/right.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/Dpad/right_hover.png b/applications/system/subghz_remote/icons/remote_scene/Dpad/right_hover.png new file mode 100644 index 000000000..2d53ce701 Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/Dpad/right_hover.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/Dpad/up.png b/applications/system/subghz_remote/icons/remote_scene/Dpad/up.png new file mode 100644 index 000000000..cd032ed12 Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/Dpad/up.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/Dpad/up_hover.png b/applications/system/subghz_remote/icons/remote_scene/Dpad/up_hover.png new file mode 100644 index 000000000..8bc334750 Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/Dpad/up_hover.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/statusbar/External_antenna_20x12.png b/applications/system/subghz_remote/icons/remote_scene/statusbar/External_antenna_20x12.png new file mode 100644 index 000000000..940087071 Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/statusbar/External_antenna_20x12.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/statusbar/Internal_antenna_20x12.png b/applications/system/subghz_remote/icons/remote_scene/statusbar/Internal_antenna_20x12.png new file mode 100644 index 000000000..a8a5be09f Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/statusbar/Internal_antenna_20x12.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/statusbar/Status_cube_14x14.png b/applications/system/subghz_remote/icons/remote_scene/statusbar/Status_cube_14x14.png new file mode 100644 index 000000000..f1e72cbbd Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/statusbar/Status_cube_14x14.png differ diff --git a/applications/system/subghz_remote/icons/remote_scene/statusbar/status_bar.png b/applications/system/subghz_remote/icons/remote_scene/statusbar/status_bar.png new file mode 100644 index 000000000..8136fe32e Binary files /dev/null and b/applications/system/subghz_remote/icons/remote_scene/statusbar/status_bar.png differ diff --git a/applications/system/subghz_remote/scenes/subrem_scene_config.h b/applications/system/subghz_remote/scenes/subrem_scene_config.h index 08486be74..56fe641a6 100644 --- a/applications/system/subghz_remote/scenes/subrem_scene_config.h +++ b/applications/system/subghz_remote/scenes/subrem_scene_config.h @@ -6,4 +6,7 @@ ADD_SCENE(subrem, edit_submenu, EditSubMenu) ADD_SCENE(subrem, edit_label, EditLabel) ADD_SCENE(subrem, open_sub_file, OpenSubFile) ADD_SCENE(subrem, edit_preview, EditPreview) -ADD_SCENE(subrem, enter_new_name, EnterNewName) \ No newline at end of file +ADD_SCENE(subrem, enter_new_name, EnterNewName) +#ifdef FW_ORIGIN_Official +ADD_SCENE(subrem, fw_warning, FwWarning) +#endif \ No newline at end of file diff --git a/applications/system/subghz_remote/scenes/subrem_scene_edit_label.c b/applications/system/subghz_remote/scenes/subrem_scene_edit_label.c index baec60145..25c10bb46 100644 --- a/applications/system/subghz_remote/scenes/subrem_scene_edit_label.c +++ b/applications/system/subghz_remote/scenes/subrem_scene_edit_label.c @@ -29,7 +29,7 @@ void subrem_scene_edit_label_widget_callback(GuiButtonType result, InputType typ void subrem_scene_edit_label_on_enter(void* context) { SubGhzRemoteApp* app = context; - SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chusen_sub]; + SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chosen_sub]; FuriString* temp_str = furi_string_alloc(); @@ -53,9 +53,9 @@ void subrem_scene_edit_label_on_enter(void* context) { app->file_name_tmp, 25, false); - +#ifndef FW_ORIGIN_Official text_input_set_minimum_length(app->text_input, 0); - +#endif widget_add_string_element( app->widget, 63, 12, AlignCenter, AlignCenter, FontPrimary, "Empty Label Name"); widget_add_string_element( @@ -76,7 +76,7 @@ void subrem_scene_edit_label_on_enter(void* context) { bool subrem_scene_edit_label_on_event(void* context, SceneManagerEvent event) { SubGhzRemoteApp* app = context; - FuriString* label = app->map_preset->subs_preset[app->chusen_sub]->label; + FuriString* label = app->map_preset->subs_preset[app->chosen_sub]->label; if(event.type == SceneManagerEventTypeBack) { if(scene_manager_get_scene_state(app->scene_manager, SubRemSceneEditLabel) == diff --git a/applications/system/subghz_remote/scenes/subrem_scene_edit_menu.c b/applications/system/subghz_remote/scenes/subrem_scene_edit_menu.c index a8882009a..bc54311c5 100644 --- a/applications/system/subghz_remote/scenes/subrem_scene_edit_menu.c +++ b/applications/system/subghz_remote/scenes/subrem_scene_edit_menu.c @@ -101,7 +101,7 @@ bool subrem_scene_edit_menu_on_event(void* context, SceneManagerEvent event) { return true; } else if(event.event == SubRemCustomEventViewEditMenuEdit) { - app->chusen_sub = subrem_view_edit_menu_get_index(app->subrem_edit_menu); + app->chosen_sub = subrem_view_edit_menu_get_index(app->subrem_edit_menu); scene_manager_set_scene_state( app->scene_manager, SubRemSceneEditSubMenu, EditSubmenuIndexEditLabel); scene_manager_next_scene(app->scene_manager, SubRemSceneEditSubMenu); diff --git a/applications/system/subghz_remote/scenes/subrem_scene_fw_warning.c b/applications/system/subghz_remote/scenes/subrem_scene_fw_warning.c new file mode 100644 index 000000000..de473b72c --- /dev/null +++ b/applications/system/subghz_remote/scenes/subrem_scene_fw_warning.c @@ -0,0 +1,129 @@ +#include "../subghz_remote_app_i.h" +#include "../helpers/subrem_custom_event.h" +#ifdef FW_ORIGIN_Official +typedef enum { + SceneFwWarningStateAttention, + SceneFwWarningStateAccept, +} SceneFwWarningState; + +static void subrem_scene_fw_warning_widget_render(SubGhzRemoteApp* app, SceneFwWarningState state); + +static void + subrem_scene_fw_warning_widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + + if(type == InputTypeShort) { + SubRemCustomEvent event = SubRemCustomEventSceneFwWarningExit; + + switch(scene_manager_get_scene_state(app->scene_manager, SubRemSceneFwWarning)) { + case SceneFwWarningStateAttention: + if(result == GuiButtonTypeRight) { + event = SubRemCustomEventSceneFwWarningNext; + } + break; + + case SceneFwWarningStateAccept: + if(result == GuiButtonTypeRight) { + event = SubRemCustomEventSceneFwWarningContinue; + } + + break; + } + + view_dispatcher_send_custom_event(app->view_dispatcher, event); + } +} + +static void + subrem_scene_fw_warning_widget_render(SubGhzRemoteApp* app, SceneFwWarningState state) { + furi_assert(app); + Widget* widget = app->widget; + + widget_reset(widget); + + switch(state) { + case SceneFwWarningStateAttention: + widget_add_button_element( + widget, GuiButtonTypeLeft, "Exit", subrem_scene_fw_warning_widget_callback, app); + widget_add_button_element( + widget, GuiButtonTypeRight, "Continue", subrem_scene_fw_warning_widget_callback, app); + widget_add_string_element( + widget, 64, 12, AlignCenter, AlignBottom, FontPrimary, "Not official FW"); + widget_add_string_multiline_element( + widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + "You are using custom firmware\nPlease download a compatible\nversion of the application"); + break; + + case SceneFwWarningStateAccept: + widget_add_button_element( + widget, GuiButtonTypeLeft, "Exit", subrem_scene_fw_warning_widget_callback, app); + widget_add_button_element( + widget, GuiButtonTypeRight, "Accept", subrem_scene_fw_warning_widget_callback, app); + widget_add_string_element( + widget, 64, 12, AlignCenter, AlignBottom, FontPrimary, "Not official FW"); + widget_add_string_multiline_element( + widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + "Yes, I understand that\nthe application can\nbreak my subghz key file"); + break; + } +} + +void subrem_scene_fw_warning_on_enter(void* context) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneFwWarning, SceneFwWarningStateAttention); + + subrem_scene_fw_warning_widget_render(app, SceneFwWarningStateAttention); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDWidget); +} + +bool subrem_scene_fw_warning_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeBack) { + consumed = true; + } else if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubRemCustomEventSceneFwWarningExit) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + consumed = true; + } else if(event.event == SubRemCustomEventSceneFwWarningNext) { + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneFwWarning, SceneFwWarningStateAccept); + subrem_scene_fw_warning_widget_render(app, SceneFwWarningStateAccept); + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDWidget); + consumed = true; + } else if(event.event == SubRemCustomEventSceneFwWarningContinue) { + scene_manager_previous_scene(app->scene_manager); + consumed = true; + } + } + + return consumed; +} + +void subrem_scene_fw_warning_on_exit(void* context) { + furi_assert(context); + + SubGhzRemoteApp* app = context; + widget_reset(app->widget); +} +#endif \ No newline at end of file diff --git a/applications/system/subghz_remote/scenes/subrem_scene_open_sub_file.c b/applications/system/subghz_remote/scenes/subrem_scene_open_sub_file.c index 663e80ba7..d34f01b99 100644 --- a/applications/system/subghz_remote/scenes/subrem_scene_open_sub_file.c +++ b/applications/system/subghz_remote/scenes/subrem_scene_open_sub_file.c @@ -9,7 +9,7 @@ void subrem_scene_open_sub_file_error_popup_callback(void* context) { SubRemLoadSubState subrem_scene_open_sub_file_dialog(SubGhzRemoteApp* app) { furi_assert(app); - SubRemSubFilePreset* sub = app->map_preset->subs_preset[app->chusen_sub]; + SubRemSubFilePreset* sub = app->map_preset->subs_preset[app->chosen_sub]; FuriString* temp_file_path = furi_string_alloc(); @@ -48,8 +48,8 @@ SubRemLoadSubState subrem_scene_open_sub_file_dialog(SubGhzRemoteApp* app) { furi_record_close(RECORD_STORAGE); if(ret == SubRemLoadSubStateOK) { - subrem_sub_file_preset_free(app->map_preset->subs_preset[app->chusen_sub]); - app->map_preset->subs_preset[app->chusen_sub] = sub_candidate; + subrem_sub_file_preset_free(app->map_preset->subs_preset[app->chosen_sub]); + app->map_preset->subs_preset[app->chosen_sub] = sub_candidate; app->map_not_saved = true; } else { subrem_sub_file_preset_free(sub_candidate); diff --git a/applications/system/subghz_remote/scenes/subrem_scene_remote.c b/applications/system/subghz_remote/scenes/subrem_scene_remote.c index e8d57dae7..efe289871 100644 --- a/applications/system/subghz_remote/scenes/subrem_scene_remote.c +++ b/applications/system/subghz_remote/scenes/subrem_scene_remote.c @@ -66,19 +66,19 @@ bool subrem_scene_remote_on_event(void* context, SceneManagerEvent event) { // Start sending sub subrem_tx_stop_sub(app, true); - uint8_t chusen_sub = subrem_scene_remote_event_to_index(event.event); - app->chusen_sub = chusen_sub; + uint8_t chosen_sub = subrem_scene_remote_event_to_index(event.event); + app->chosen_sub = chosen_sub; subrem_view_remote_set_state( - app->subrem_remote_view, SubRemViewRemoteStateLoading, chusen_sub); + app->subrem_remote_view, SubRemViewRemoteStateLoading, chosen_sub); - if(subrem_tx_start_sub(app, app->map_preset->subs_preset[chusen_sub])) { - if(app->map_preset->subs_preset[chusen_sub]->type == SubGhzProtocolTypeRAW) { + if(subrem_tx_start_sub(app, app->map_preset->subs_preset[chosen_sub])) { + if(app->map_preset->subs_preset[chosen_sub]->type == SubGhzProtocolTypeRAW) { subghz_txrx_set_raw_file_encoder_worker_callback_end( app->txrx, subrem_scene_remote_raw_callback_end_tx, app); } subrem_view_remote_set_state( - app->subrem_remote_view, SubRemViewRemoteStateSending, chusen_sub); + app->subrem_remote_view, SubRemViewRemoteStateSending, chosen_sub); notification_message(app->notifications, &sequence_blink_start_magenta); } else { subrem_view_remote_set_state( diff --git a/applications/system/subghz_remote/subghz_remote_app.c b/applications/system/subghz_remote/subghz_remote_app.c index 1af39f57d..473e3c012 100644 --- a/applications/system/subghz_remote/subghz_remote_app.c +++ b/applications/system/subghz_remote/subghz_remote_app.c @@ -1,4 +1,5 @@ #include "subghz_remote_app_i.h" +#include static bool subghz_remote_app_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -174,7 +175,9 @@ int32_t subghz_remote_app(void* arg) { subghz_remote_make_app_folder(subghz_remote_app); bool map_loaded = false; - +#ifdef FW_ORIGIN_Official + const bool fw_ofw = strcmp(version_get_firmware_origin(version_get()), "Official") == 0; +#endif if((arg != NULL) && (strlen(arg) != 0)) { furi_string_set(subghz_remote_app->file_path, (const char*)arg); SubRemLoadMapState load_state = subrem_map_file_load( @@ -193,8 +196,19 @@ int32_t subghz_remote_app(void* arg) { } else { furi_string_set(subghz_remote_app->file_path, SUBREM_APP_FOLDER); scene_manager_next_scene(subghz_remote_app->scene_manager, SubRemSceneStart); +#ifdef FW_ORIGIN_Official + if(fw_ofw) { + scene_manager_next_scene(subghz_remote_app->scene_manager, SubRemSceneOpenMapFile); + } + } + + if(!fw_ofw) { + scene_manager_next_scene(subghz_remote_app->scene_manager, SubRemSceneFwWarning); + } +#else scene_manager_next_scene(subghz_remote_app->scene_manager, SubRemSceneOpenMapFile); } +#endif view_dispatcher_run(subghz_remote_app->view_dispatcher); diff --git a/applications/system/subghz_remote/subghz_remote_app_i.c b/applications/system/subghz_remote/subghz_remote_app_i.c index 82e762c2a..45321441d 100644 --- a/applications/system/subghz_remote/subghz_remote_app_i.c +++ b/applications/system/subghz_remote/subghz_remote_app_i.c @@ -3,12 +3,9 @@ #include #include "helpers/txrx/subghz_txrx.h" - -// #include -// #include - -#include +#ifndef FW_ORIGIN_Official #include +#endif #define TAG "SubGhzRemote" @@ -199,7 +196,7 @@ void subrem_save_active_sub(void* context) { furi_assert(context); SubGhzRemoteApp* app = context; - SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chusen_sub]; + SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chosen_sub]; subrem_save_protocol_to_file( sub_preset->fff_data, furi_string_get_cstr(sub_preset->file_path)); } @@ -225,9 +222,9 @@ bool subrem_tx_start_sub(SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset) sub_preset->freq_preset.frequency, NULL, 0); - +#ifndef FW_ORIGIN_Official subghz_custom_btns_reset(); - +#endif if(subghz_txrx_tx_start(app->txrx, sub_preset->fff_data) == SubGhzTxRxStartTxStateOk) { ret = true; } @@ -238,16 +235,16 @@ bool subrem_tx_start_sub(SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset) bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced) { furi_assert(app); - SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chusen_sub]; + SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chosen_sub]; if(forced || (sub_preset->type != SubGhzProtocolTypeRAW)) { subghz_txrx_stop(app->txrx); - +#ifndef FW_ORIGIN_Official if(sub_preset->type == SubGhzProtocolTypeDynamic) { subghz_txrx_reset_dynamic_and_custom_btns(app->txrx); } subghz_custom_btns_reset(); - +#endif return true; } diff --git a/applications/system/subghz_remote/subghz_remote_app_i.h b/applications/system/subghz_remote/subghz_remote_app_i.h index 42e9576fd..825e23121 100644 --- a/applications/system/subghz_remote/subghz_remote_app_i.h +++ b/applications/system/subghz_remote/subghz_remote_app_i.h @@ -6,6 +6,10 @@ #include "helpers/txrx/subghz_txrx.h" +#if __has_include("subghz_remote_icons.h") +#include "subghz_remote_icons.h" +#endif + #include #include "views/remote.h" @@ -50,7 +54,7 @@ typedef struct { bool map_not_saved; - uint8_t chusen_sub; + uint8_t chosen_sub; } SubGhzRemoteApp; SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app); @@ -65,4 +69,4 @@ void subrem_map_preset_reset(SubRemMapPreset* map_preset); bool subrem_save_map_to_file(SubGhzRemoteApp* app); -void subrem_save_active_sub(void* context); +void subrem_save_active_sub(void* context); \ No newline at end of file diff --git a/applications/system/subghz_remote/views/edit_menu.c b/applications/system/subghz_remote/views/edit_menu.c index 9b88182b5..6dd590ed0 100644 --- a/applications/system/subghz_remote/views/edit_menu.c +++ b/applications/system/subghz_remote/views/edit_menu.c @@ -19,7 +19,7 @@ typedef struct { FuriString* file_path; SubRemLoadSubState sub_state; - uint8_t chusen; + uint8_t chosen; } SubRemViewEditMenuModel; void subrem_view_edit_menu_set_callback( @@ -44,7 +44,7 @@ void subrem_view_edit_menu_add_data_to_show( subrem_view_edit_remote->view, SubRemViewEditMenuModel * model, { - model->chusen = index; + model->chosen = index; if(!furi_string_empty(label)) { furi_string_set(model->label, label); } else { @@ -63,7 +63,7 @@ uint8_t subrem_view_edit_menu_get_index(SubRemViewEditMenu* subrem_view_edit_rem with_view_model( subrem_view_edit_remote->view, SubRemViewEditMenuModel * model, - { index = model->chusen; }, + { index = model->chosen; }, true); return index; } @@ -89,7 +89,7 @@ void subrem_view_edit_menu_draw(Canvas* canvas, SubRemViewEditMenuModel* model) // Draw btn name canvas_set_font(canvas, FontPrimary); - switch(model->chusen) { + switch(model->chosen) { case SubRemSubKeyNameUp: canvas_draw_str(canvas, 3, FRAME_HEIGHT - 2, "UP"); break; @@ -129,10 +129,10 @@ void subrem_view_edit_menu_draw(Canvas* canvas, SubRemViewEditMenuModel* model) // Draw arrow canvas_set_color(canvas, ColorBlack); - if(model->chusen != 0) { + if(model->chosen != 0) { canvas_draw_icon(canvas, 119, 13, &I_Pin_arrow_up_7x9); } - if(model->chusen != 4) { + if(model->chosen != 4) { canvas_draw_icon_ex(canvas, 119, 42, &I_Pin_arrow_up_7x9, IconRotation180); } @@ -200,8 +200,8 @@ bool subrem_view_edit_menu_input(InputEvent* event, void* context) { subrem_view_edit_menu->view, SubRemViewEditMenuModel * model, { - if(model->chusen > 0) { - model->chusen -= 1; + if(model->chosen > 0) { + model->chosen -= 1; }; }, true); @@ -213,8 +213,8 @@ bool subrem_view_edit_menu_input(InputEvent* event, void* context) { subrem_view_edit_menu->view, SubRemViewEditMenuModel * model, { - if(model->chusen < 4) { - model->chusen += 1; + if(model->chosen < 4) { + model->chosen += 1; }; }, true); @@ -263,7 +263,7 @@ SubRemViewEditMenu* subrem_view_edit_menu_alloc() { model->label = furi_string_alloc(); // furi_string_alloc_set_str("LABEL"); model->file_path = furi_string_alloc(); // furi_string_alloc_set_str("FILE_PATH"); - model->chusen = 0; + model->chosen = 0; }, true); return subrem_view_edit_menu; diff --git a/applications/system/subghz_remote/views/remote.c b/applications/system/subghz_remote/views/remote.c index bdf4b89c3..2523d1977 100644 --- a/applications/system/subghz_remote/views/remote.c +++ b/applications/system/subghz_remote/views/remote.c @@ -8,7 +8,7 @@ #define SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH 30 #define SUBREM_VIEW_REMOTE_LEFT_OFFSET 10 -#define SUBREM_VIEW_REMOTE_RIGHT_OFFSET 22 +#define SUBREM_VIEW_REMOTE_RIGHT_OFFSET 0 struct SubRemViewRemote { View* view; @@ -120,26 +120,43 @@ void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); - canvas_clear(canvas); + // Statusbar + canvas_draw_icon(canvas, 0, 0, &I_status_bar); + if(model->state == SubRemViewRemoteStateOFF) { + canvas_invert_color(canvas); + canvas_draw_rbox(canvas, 12, 0, 52 - 12, 13, 2); + canvas_invert_color(canvas); + canvas_draw_rframe(canvas, 12, 0, 52 - 12, 13, 2); + canvas_draw_str_aligned(canvas, 32, 3, AlignCenter, AlignTop, "Preview"); + } else { + canvas_draw_icon( + canvas, + 0, + 2, + (model->is_external) ? &I_External_antenna_20x12 : &I_Internal_antenna_20x12); + canvas_draw_icon(canvas, 50, 0, &I_Status_cube_14x14); + if(model->state == SubRemViewRemoteStateSending) { + canvas_draw_icon_ex(canvas, 52, 3, &I_Pin_arrow_up_7x9, IconRotation90); + } + } //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); + const uint8_t list_y = 14; + canvas_draw_icon(canvas, 1, list_y + 5, &I_ButtonUp_7x4); + canvas_draw_icon(canvas, 1, list_y + 15, &I_ButtonDown_7x4); + canvas_draw_icon(canvas, 2, list_y + 23, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 2, list_y + 33, &I_ButtonRight_4x7); + canvas_draw_icon(canvas, 0, list_y + 42, &I_Ok_btn_9x9); //Labels canvas_set_font(canvas, FontSecondary); - uint8_t y = 0; + uint8_t y = list_y; for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { elements_text_box( canvas, SUBREM_VIEW_REMOTE_LEFT_OFFSET, y + 2, - 126 - SUBREM_VIEW_REMOTE_LEFT_OFFSET - SUBREM_VIEW_REMOTE_RIGHT_OFFSET, + 64 - SUBREM_VIEW_REMOTE_LEFT_OFFSET - SUBREM_VIEW_REMOTE_RIGHT_OFFSET, 12, AlignLeft, AlignBottom, @@ -148,52 +165,50 @@ void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { y += 10; } - if(model->state == SubRemViewRemoteStateOFF) { - elements_button_left(canvas, "Back"); - elements_button_right(canvas, "Save"); - } else { - canvas_draw_str_aligned(canvas, 11, 62, AlignLeft, AlignBottom, "Hold=Exit."); - canvas_draw_str_aligned( - canvas, 126, 62, AlignRight, AlignBottom, ((model->is_external) ? "Ext" : "Int")); - } + if(model->state != SubRemViewRemoteStateOFF) { + // D-pad 59x62 + const uint8_t d_pad_x = 3; + const uint8_t d_pad_y = 66; - //Status text and indicator - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); + canvas_draw_icon(canvas, d_pad_x + 1 * (19 + 1), d_pad_y + 0 * (20 + 1), &I_up); - if(model->state == SubRemViewRemoteStateIdle || model->state == SubRemViewRemoteStateOFF) { - 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; + canvas_draw_icon(canvas, d_pad_x + 0 * (19 + 1), d_pad_y + 1 * (20 + 1), &I_left); + canvas_draw_icon(canvas, d_pad_x + 1 * (19 + 1), d_pad_y + 1 * (20 + 1), &I_ok); + canvas_draw_icon(canvas, d_pad_x + 2 * (19 + 1), d_pad_y + 1 * (20 + 1), &I_right); + + canvas_draw_icon(canvas, d_pad_x + 1 * (19 + 1), d_pad_y + 2 * (20 + 1), &I_down); + if(model->state == SubRemViewRemoteStateSending) { + switch(model->pressed_btn) { + case SubRemSubKeyNameUp: + canvas_draw_icon( + canvas, d_pad_x + 1 * (19 + 1), d_pad_y + 0 * (20 + 1), &I_up_hover); + break; + case SubRemSubKeyNameDown: + canvas_draw_icon( + canvas, d_pad_x + 1 * (19 + 1), d_pad_y + 2 * (20 + 1), &I_down_hover); + break; + case SubRemSubKeyNameLeft: + canvas_draw_icon( + canvas, d_pad_x + 0 * (19 + 1), d_pad_y + 1 * (20 + 1), &I_left_hover); + break; + case SubRemSubKeyNameRight: + canvas_draw_icon( + canvas, d_pad_x + 2 * (19 + 1), d_pad_y + 1 * (20 + 1), &I_right_hover); + break; + case SubRemSubKeyNameOk: + canvas_draw_icon( + canvas, d_pad_x + 1 * (19 + 1), d_pad_y + 1 * (20 + 1), &I_ok_hover); + break; + default: + break; + } } + } else { + canvas_draw_icon(canvas, 2, 128 - 11, &I_ButtonLeft_4x7); + canvas_draw_str_aligned(canvas, 8, 128 - 4, AlignLeft, AlignBottom, "Back"); - 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; - } + canvas_draw_icon(canvas, 58, 128 - 11, &I_ButtonRight_4x7); + canvas_draw_str_aligned(canvas, 56, 128 - 4, AlignRight, AlignBottom, "Save"); } } @@ -271,6 +286,7 @@ SubRemViewRemote* subrem_view_remote_alloc() { view_allocate_model( subrem_view_remote->view, ViewModelTypeLocking, sizeof(SubRemViewRemoteModel)); view_set_context(subrem_view_remote->view, subrem_view_remote); + view_set_orientation(subrem_view_remote->view, ViewOrientationVertical); 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); diff --git a/applications/system/text_viewer/application.fam b/applications/system/text_viewer/application.fam index 6bdfe7c89..cbbe8b2d4 100644 --- a/applications/system/text_viewer/application.fam +++ b/applications/system/text_viewer/application.fam @@ -12,6 +12,6 @@ App( fap_category="Tools", fap_icon_assets="icons", fap_author="@Willy-JL", # Original by @kowalski7cc & @kyhwana, new has code borrowed from archive > show - fap_version="1.0", + fap_version="1.5", fap_description="Text viewer application", ) diff --git a/lib/nfc/helpers/nfc_data_generator.c b/lib/nfc/helpers/nfc_data_generator.c index 011f9f6db..21f062605 100644 --- a/lib/nfc/helpers/nfc_data_generator.c +++ b/lib/nfc/helpers/nfc_data_generator.c @@ -329,9 +329,23 @@ static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) { static void nfc_generate_mf_classic_common(MfClassicData* data, uint8_t uid_len, MfClassicType type) { data->iso14443_3a_data->uid_len = uid_len; - data->iso14443_3a_data->atqa[0] = 0x44; + data->iso14443_3a_data->atqa[0] = 0x00; data->iso14443_3a_data->atqa[1] = 0x00; - data->iso14443_3a_data->sak = 0x08; + data->iso14443_3a_data->sak = 0x00; + // Calculate the proper ATQA and SAK + if(uid_len == 7) { + data->iso14443_3a_data->atqa[0] |= 0x40; + } + if(type == MfClassicType1k) { + data->iso14443_3a_data->atqa[0] |= 0x04; + data->iso14443_3a_data->sak = 0x08; + } else if(type == MfClassicType4k) { + data->iso14443_3a_data->atqa[0] |= 0x02; + data->iso14443_3a_data->sak = 0x18; + } else if(type == MfClassicTypeMini) { + data->iso14443_3a_data->atqa[0] |= 0x08; + data->iso14443_3a_data->sak = 0x09; + } data->type = type; } @@ -343,6 +357,11 @@ static void nfc_generate_mf_classic_sector_trailer(MfClassicData* data, uint8_t sec_tr->access_bits.data[2] = 0x80; sec_tr->access_bits.data[3] = 0x69; // Nice + for(int i = 0; i < 6; i++) { + sec_tr->key_a.data[i] = 0xFF; + sec_tr->key_b.data[i] = 0xFF; + } + mf_classic_set_block_read(data, block, &data->block[block]); mf_classic_set_key_found( data, mf_classic_get_sector_by_block(block), MfClassicKeyTypeA, 0xFFFFFFFFFFFF); @@ -396,41 +415,35 @@ static void nfc_generate_mf_classic(NfcDevice* nfc_device, uint8_t uid_len, MfCl uint16_t block_num = mf_classic_get_total_block_num(type); if(type == MfClassicType4k) { - // Set every block to 0xFF + // Set every block to 0x00 for(uint16_t i = 1; i < block_num; i++) { if(mf_classic_is_sector_trailer(i)) { nfc_generate_mf_classic_sector_trailer(mfc_data, i); } else { - memset(&mfc_data->block[i].data, 0xFF, 16); + memset(&mfc_data->block[i].data, 0x00, 16); } mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); } - // Set SAK to 18 - mfc_data->iso14443_3a_data->sak = 0x18; } else if(type == MfClassicType1k) { - // Set every block to 0xFF + // Set every block to 0x00 for(uint16_t i = 1; i < block_num; i++) { if(mf_classic_is_sector_trailer(i)) { nfc_generate_mf_classic_sector_trailer(mfc_data, i); } else { - memset(&mfc_data->block[i].data, 0xFF, 16); + memset(&mfc_data->block[i].data, 0x00, 16); } mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); } - // Set SAK to 08 - mfc_data->iso14443_3a_data->sak = 0x08; } else if(type == MfClassicTypeMini) { - // Set every block to 0xFF + // Set every block to 0x00 for(uint16_t i = 1; i < block_num; i++) { if(mf_classic_is_sector_trailer(i)) { nfc_generate_mf_classic_sector_trailer(mfc_data, i); } else { - memset(&mfc_data->block[i].data, 0xFF, 16); + memset(&mfc_data->block[i].data, 0x00, 16); } mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); } - // Set SAK to 09 - mfc_data->iso14443_3a_data->sak = 0x09; } nfc_generate_mf_classic_block_0( diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index a7c4fe1a6..6475cce43 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -76,7 +76,7 @@ static const FuriHalNfcTech nfc_tech_table[NfcModeNum][NfcTechNum] = { [NfcTechIso14443a] = FuriHalNfcTechIso14443a, [NfcTechIso14443b] = FuriHalNfcTechInvalid, [NfcTechIso15693] = FuriHalNfcTechIso15693, - [NfcTechFelica] = FuriHalNfcTechInvalid, + [NfcTechFelica] = FuriHalNfcTechFelica, }, }; @@ -646,4 +646,20 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { return ret; } +NfcError nfc_felica_listener_set_sensf_res_data( + Nfc* instance, + const uint8_t* idm, + const uint8_t idm_len, + const uint8_t* pmm, + const uint8_t pmm_len) { + furi_assert(instance); + furi_assert(idm); + furi_assert(pmm); + + FuriHalNfcError error = + furi_hal_nfc_felica_listener_set_sensf_res_data(idm, idm_len, pmm, pmm_len); + instance->comm_state = NfcCommStateIdle; + return nfc_process_hal_error(error); +} + #endif // APP_UNIT_TESTS diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h index 1e8f315a5..4f7980b02 100644 --- a/lib/nfc/nfc.h +++ b/lib/nfc/nfc.h @@ -351,6 +351,25 @@ NfcError nfc_iso14443a_listener_set_col_res_data( uint8_t* atqa, uint8_t sak); +/** + * @brief Set FeliCa collision resolution parameters in listener mode. + * + * Configures the NFC hardware for automatic collision resolution. + * + * @param[in,out] instance pointer to the instance to be configured. + * @param[in] idm pointer to a byte array containing the IDm. + * @param[in] idm_len IDm length in bytes. + * @param[in] pmm pointer to a byte array containing the PMm. + * @param[in] pmm_len PMm length in bytes. + * @returns NfcErrorNone on success, any other error code on failure. +*/ +NfcError nfc_felica_listener_set_sensf_res_data( + Nfc* instance, + const uint8_t* idm, + const uint8_t idm_len, + const uint8_t* pmm, + const uint8_t pmm_len); + /** * @brief Send ISO15693 Start of Frame pattern in listener mode * diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index da9d2294e..31e040b8a 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -14,6 +14,8 @@ extern "C" { #define FELICA_FDT_POLL_FC (10000U) #define FELICA_POLL_POLL_MIN_US (1280U) +#define FELICA_FDT_LISTEN_FC (1172) + #define FELICA_SYSTEM_CODE_CODE (0xFFFFU) #define FELICA_TIME_SLOT_1 (0x00U) #define FELICA_TIME_SLOT_2 (0x01U) diff --git a/lib/nfc/protocols/felica/felica_listener.c b/lib/nfc/protocols/felica/felica_listener.c new file mode 100644 index 000000000..4e6c05785 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener.c @@ -0,0 +1,79 @@ +#include "felica_listener_i.h" + +#include "nfc/protocols/nfc_listener_base.h" + +#define FELICA_LISTENER_MAX_BUFFER_SIZE (64) +#define TAG "Felica" + +FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) { + furi_assert(nfc); + furi_assert(data); + + FelicaListener* instance = malloc(sizeof(FelicaListener)); + instance->nfc = nfc; + instance->data = data; + instance->tx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE); + + nfc_set_fdt_listen_fc(instance->nfc, FELICA_FDT_LISTEN_FC); + + nfc_config(instance->nfc, NfcModeListener, NfcTechFelica); + nfc_felica_listener_set_sensf_res_data( + nfc, data->idm.data, sizeof(data->idm), data->pmm.data, sizeof(data->pmm)); + + return instance; +} + +void felica_listener_free(FelicaListener* instance) { + furi_assert(instance); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +void felica_listener_set_callback( + FelicaListener* listener, + NfcGenericCallback callback, + void* context) { + UNUSED(listener); + UNUSED(callback); + UNUSED(context); +} + +const FelicaData* felica_listener_get_data(const FelicaListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +NfcCommand felica_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + FelicaListener* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypeListenerActivated) { + instance->state = Felica_ListenerStateActivated; + FURI_LOG_D(TAG, "Activated"); + } else if(nfc_event->type == NfcEventTypeFieldOff) { + instance->state = Felica_ListenerStateIdle; + FURI_LOG_D(TAG, "Field Off"); + } else if(nfc_event->type == NfcEventTypeRxEnd) { + FURI_LOG_D(TAG, "Rx Done"); + } + return command; +} + +const NfcListenerBase nfc_listener_felica = { + .alloc = (NfcListenerAlloc)felica_listener_alloc, + .free = (NfcListenerFree)felica_listener_free, + .set_callback = (NfcListenerSetCallback)felica_listener_set_callback, + .get_data = (NfcListenerGetData)felica_listener_get_data, + .run = (NfcListenerRun)felica_listener_run, +}; \ No newline at end of file diff --git a/lib/nfc/protocols/felica/felica_listener.h b/lib/nfc/protocols/felica/felica_listener.h new file mode 100644 index 000000000..d210befa5 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener.h @@ -0,0 +1,14 @@ +#pragma once + +#include "felica.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FelicaListener FelicaListener; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/felica/felica_listener_defs.h b/lib/nfc/protocols/felica/felica_listener_defs.h new file mode 100644 index 000000000..19b252be5 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcListenerBase nfc_listener_felica; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/felica/felica_listener_i.h b/lib/nfc/protocols/felica/felica_listener_i.h new file mode 100644 index 000000000..4fa25a162 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener_i.h @@ -0,0 +1,21 @@ +#include "felica_listener.h" + +#include + +typedef enum { + Felica_ListenerStateIdle, + Felica_ListenerStateActivated, +} FelicaListenerState; + +struct FelicaListener { + Nfc* nfc; + FelicaData* data; + FelicaListenerState state; + + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent generic_event; + NfcGenericCallback callback; + void* context; +}; \ No newline at end of file diff --git a/lib/nfc/protocols/mf_classic/mf_classic.c b/lib/nfc/protocols/mf_classic/mf_classic.c index 400cf0d7f..e68e8c718 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.c +++ b/lib/nfc/protocols/mf_classic/mf_classic.c @@ -606,6 +606,7 @@ static bool mf_classic_is_allowed_access_sector_trailer( uint8_t* access_bits_arr = sec_tr->access_bits.data; uint8_t AC = ((access_bits_arr[1] >> 5) & 0x04) | ((access_bits_arr[2] >> 2) & 0x02) | ((access_bits_arr[2] >> 7) & 0x01); + FURI_LOG_T("NFC", "AC: %02X", AC); switch(action) { case MfClassicActionKeyARead: { @@ -615,20 +616,20 @@ static bool mf_classic_is_allowed_access_sector_trailer( case MfClassicActionKeyBWrite: { return ( (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x01)) || - (key_type == MfClassicKeyTypeB && (AC == 0x04 || AC == 0x03))); + (key_type == MfClassicKeyTypeB && + (AC == 0x00 || AC == 0x04 || AC == 0x03 || AC == 0x01))); } case MfClassicActionKeyBRead: { - return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01)); + return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x02 || AC == 0x01)); } case MfClassicActionACRead: { - return ( - (key_type == MfClassicKeyTypeA) || - (key_type == MfClassicKeyTypeB && !(AC == 0x00 || AC == 0x02 || AC == 0x01))); + return ((key_type == MfClassicKeyTypeA) || (key_type == MfClassicKeyTypeB)); } case MfClassicActionACWrite: { return ( (key_type == MfClassicKeyTypeA && (AC == 0x01)) || - (key_type == MfClassicKeyTypeB && (AC == 0x03 || AC == 0x05))); + (key_type == MfClassicKeyTypeB && (AC == 0x01 || AC == 0x03 || AC == 0x05))); } default: return false; diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h index f180411df..146e6a6f1 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.h +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -19,6 +19,8 @@ extern "C" { #define MF_CLASSIC_CMD_HALT_LSB (0x00) #define MF_CLASSIC_CMD_ACK (0x0A) #define MF_CLASSIC_CMD_NACK (0x00) +#define MF_CLASSIC_CMD_NACK_TRANSFER_INVALID (0x04) +#define MF_CLASSIC_CMD_NACK_TRANSFER_CRC_ERROR (0x01) #define MF_CLASSIC_TOTAL_SECTORS_MAX (40) #define MF_CLASSIC_TOTAL_BLOCKS_MAX (256) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c index f7bd5b3f4..fb12ba8a9 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -34,6 +34,7 @@ static void mf_classic_listener_reset_state(MfClassicListener* instance) { instance->cmd_in_progress = false; instance->current_cmd_handler_idx = 0; instance->transfer_value = 0; + instance->transfer_valid = false; instance->value_cmd = MfClassicValueCommandInvalid; } @@ -154,7 +155,7 @@ static MfClassicListenerCommand uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt)); uint32_t secret_poller = ar_num ^ crypto1_word(instance->crypto, 0, 0); if(secret_poller != prng_successor(nt_num, 64)) { - FURI_LOG_D( + FURI_LOG_T( TAG, "Wrong reader key: %08lX != %08lX", secret_poller, prng_successor(nt_num, 64)); mf_classic_listener_reset_state(instance); break; @@ -272,6 +273,17 @@ static MfClassicListenerCommand mf_classic_listener_write_block_second_part_hand if(mf_classic_is_sector_trailer(block_num)) { MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)█ + + // Check if any writing is allowed + if(!mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionKeyAWrite) && + !mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionKeyBWrite) && + !mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionACWrite)) { + break; + } + if(mf_classic_is_allowed_access( instance->data, block_num, key_type, MfClassicActionKeyAWrite)) { bit_buffer_write_bytes_mid(buff, sec_tr->key_a.data, 0, sizeof(MfClassicKey)); @@ -338,6 +350,7 @@ static MfClassicListenerCommand break; } + instance->transfer_valid = true; instance->cmd_in_progress = true; instance->current_cmd_handler_idx++; command = MfClassicListenerCommandAck; @@ -382,6 +395,7 @@ static MfClassicListenerCommand } instance->transfer_value += data; + instance->transfer_valid = true; instance->cmd_in_progress = true; instance->current_cmd_handler_idx++; @@ -411,6 +425,7 @@ static MfClassicListenerCommand mf_classic_value_to_block( instance->transfer_value, block_num, &instance->data->block[block_num]); instance->transfer_value = 0; + instance->transfer_valid = false; command = MfClassicListenerCommandAck; } while(false); @@ -581,7 +596,13 @@ NfcCommand mf_classic_listener_run(NfcGenericEvent event, void* context) { if(mfc_command == MfClassicListenerCommandAck) { mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_ACK); } else if(mfc_command == MfClassicListenerCommandNack) { - mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_NACK); + // Calculate nack based on the transfer buffer validity + uint8_t nack = MF_CLASSIC_CMD_NACK; + if(!instance->transfer_valid) { + nack += MF_CLASSIC_CMD_NACK_TRANSFER_INVALID; + } + + mf_classic_listener_send_short_frame(instance, nack); } else if(mfc_command == MfClassicListenerCommandSilent) { command = NfcCommandReset; } else if(mfc_command == MfClassicListenerCommandSleep) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h index 4b040bec1..52273be9c 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h @@ -42,6 +42,7 @@ struct MfClassicListener { // Value operation data int32_t transfer_value; + bool transfer_valid; MfClassicValueCommand value_cmd; NfcGenericEvent generic_event; diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c index 31f9bc16c..2a6167e9c 100644 --- a/lib/nfc/protocols/nfc_listener_defs.c +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -6,6 +6,7 @@ #include #include #include +#include const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a, @@ -18,4 +19,5 @@ const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolMfDesfire] = NULL, [NfcProtocolSlix] = &nfc_listener_slix, [NfcProtocolSt25tb] = NULL, + [NfcProtocolFelica] = &nfc_listener_felica, }; diff --git a/lib/subghz/protocols/pocsag.c b/lib/subghz/protocols/pocsag.c index d9dfd3fa1..aa282e4a8 100644 --- a/lib/subghz/protocols/pocsag.c +++ b/lib/subghz/protocols/pocsag.c @@ -93,6 +93,8 @@ void subghz_protocol_decoder_pocsag_free(void* context) { SubGhzProtocolDecoderPocsag* instance = context; furi_string_free(instance->msg); furi_string_free(instance->done_msg); + furi_string_free(instance->generic.result_msg); + furi_string_free(instance->generic.result_ric); free(instance); } diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index f942fe6f6..29c0d96f9 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -31,7 +31,6 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, - &subghz_protocol_honeywell, &subghz_protocol_honeywell_wdb, &subghz_protocol_magellan, &subghz_protocol_intertechno_v3, @@ -65,6 +64,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &tpms_protocol_schrader_gg4, &subghz_protocol_bin_raw, &subghz_protocol_mastercode, + &subghz_protocol_honeywell, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index de6c4cbc6..6c760a1c9 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -63,6 +63,6 @@ #include "auriol_ahfl.h" #include "pocsag.h" #include "schrader_gg4.h" -#include "honeywell.h" #include "bin_raw.h" #include "mastercode.h" +#include "honeywell.h" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index a0800a36a..cc083b2ec 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1355,6 +1355,7 @@ Function,+,furi_hal_nfc_abort,FuriHalNfcError, Function,+,furi_hal_nfc_acquire,FuriHalNfcError, Function,+,furi_hal_nfc_event_start,FuriHalNfcError, Function,+,furi_hal_nfc_event_stop,FuriHalNfcError, +Function,+,furi_hal_nfc_felica_listener_set_sensf_res_data,FuriHalNfcError,"const uint8_t*, const uint8_t, const uint8_t*, const uint8_t" Function,+,furi_hal_nfc_field_detect_start,FuriHalNfcError, Function,+,furi_hal_nfc_field_detect_stop,FuriHalNfcError, Function,+,furi_hal_nfc_field_is_present,_Bool, @@ -2402,6 +2403,7 @@ Function,+,nfc_dict_get_next_key,_Bool,"NfcDict*, uint8_t*, size_t" Function,+,nfc_dict_get_total_keys,uint32_t,NfcDict* Function,+,nfc_dict_is_key_present,_Bool,"NfcDict*, const uint8_t*, size_t" Function,+,nfc_dict_rewind,_Bool,NfcDict* +Function,+,nfc_felica_listener_set_sensf_res_data,NfcError,"Nfc*, const uint8_t*, const uint8_t, const uint8_t*, const uint8_t" Function,+,nfc_free,void,Nfc* Function,+,nfc_iso14443a_listener_set_col_res_data,NfcError,"Nfc*, uint8_t*, uint8_t, uint8_t*, uint8_t" Function,+,nfc_iso14443a_listener_tx_custom_parity,NfcError,"Nfc*, const BitBuffer*" diff --git a/targets/f7/furi_hal/furi_hal_nfc_felica.c b/targets/f7/furi_hal/furi_hal_nfc_felica.c index e4b8ac0ee..f762a0ed3 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_felica.c +++ b/targets/f7/furi_hal/furi_hal_nfc_felica.c @@ -1,6 +1,9 @@ #include "furi_hal_nfc_i.h" #include "furi_hal_nfc_tech_i.h" +// Prevent FDT timer from starting +#define FURI_HAL_NFC_FELICA_LISTENER_FDT_COMP_FC (INT32_MAX) + static FuriHalNfcError furi_hal_nfc_felica_poller_init(FuriHalSpiBusHandle* handle) { // Enable Felica mode, AM modulation st25r3916_change_reg_bits( @@ -50,6 +53,126 @@ static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(FuriHalSpiBusHandle* ha return FuriHalNfcErrorNone; } +static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* handle) { + furi_assert(handle); + st25r3916_write_reg( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); + + // Enable Target Felica mode, AM modulation + st25r3916_write_reg( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om2 | ST25R3916_REG_MODE_tr_am); + + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_BIT_RATE, + ST25R3916_REG_BIT_RATE_txrate_mask | ST25R3916_REG_BIT_RATE_rxrate_mask, + ST25R3916_REG_BIT_RATE_txrate_212 | ST25R3916_REG_BIT_RATE_rxrate_212); + + // Receive configuration + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF1, + ST25R3916_REG_RX_CONF1_lp0 | ST25R3916_REG_RX_CONF1_hz_12_80khz); + + // AGC enabled, ratio 3:1, squelch after TX + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF2, + ST25R3916_REG_RX_CONF2_agc6_3 | ST25R3916_REG_RX_CONF2_agc_m | + ST25R3916_REG_RX_CONF2_agc_en | ST25R3916_REG_RX_CONF2_sqm_dyn); + // HF operation, full gain on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00); + // No gain reduction on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00); + // 10% ASK modulation + st25r3916_write_reg(handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_am_mod_10percent); + + // Correlator setup + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s6 | ST25R3916_REG_CORR_CONF1_corr_s4 | + ST25R3916_REG_CORR_CONF1_corr_s2); + + // Sleep mode disable, 424kHz mode off + st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, 0x00); + + st25r3916_write_reg(handle, ST25R3916_REG_MASK_RX_TIMER, 0x02); + + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); + uint32_t interrupts = + (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | + ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | + ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE | + ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_WU_A_X | + ST25R3916_IRQ_MASK_WU_A); + // Clear interrupts + st25r3916_get_irq(handle); + + st25r3916_write_reg( + handle, + ST25R3916_REG_PASSIVE_TARGET, + ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a | ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p | + ST25R3916_REG_PASSIVE_TARGET_fdel_1); + // Enable interrupts + st25r3916_mask_irq(handle, ~interrupts); + st25r3916_direct_cmd(handle, ST25R3916_CMD_GOTO_SENSE); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_felica_listener_deinit(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + return FuriHalNfcErrorNone; +} + +static FuriHalNfcEvent furi_hal_nfc_felica_listener_wait_event(uint32_t timeout_ms) { + UNUSED(timeout_ms); + FuriHalNfcEvent event = furi_hal_nfc_wait_event_common(timeout_ms); + + return event; +} + +FuriHalNfcError furi_hal_nfc_felica_listener_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + UNUSED(handle); + UNUSED(tx_data); + UNUSED(tx_bits); + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_felica_listener_sleep(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_felica_listener_idle(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_felica_listener_set_sensf_res_data( + const uint8_t* idm, + const uint8_t idm_len, + const uint8_t* pmm, + const uint8_t pmm_len) { + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + // Write PT Memory + uint8_t pt_memory[19] = {}; + pt_memory[2] = 0x01; + memcpy(pt_memory + 3, idm, idm_len); + memcpy(pt_memory + 3 + idm_len, pmm, pmm_len); + st25r3916_write_ptf_mem(handle, pt_memory, sizeof(pt_memory)); + return FuriHalNfcErrorNone; +} + const FuriHalNfcTechBase furi_hal_nfc_felica = { .poller = { @@ -65,5 +188,18 @@ const FuriHalNfcTechBase furi_hal_nfc_felica = { .rx = furi_hal_nfc_common_fifo_rx, }, - .listener = {}, + .listener = + { + .compensation = + { + .fdt = FURI_HAL_NFC_FELICA_LISTENER_FDT_COMP_FC, + }, + .init = furi_hal_nfc_felica_listener_init, + .deinit = furi_hal_nfc_felica_listener_deinit, + .wait_event = furi_hal_nfc_felica_listener_wait_event, + .tx = furi_hal_nfc_felica_listener_tx, + .rx = furi_hal_nfc_common_fifo_rx, + .sleep = furi_hal_nfc_felica_listener_sleep, + .idle = furi_hal_nfc_felica_listener_idle, + }, }; diff --git a/targets/furi_hal_include/furi_hal_nfc.h b/targets/furi_hal_include/furi_hal_nfc.h index ad4080e26..3d145d100 100644 --- a/targets/furi_hal_include/furi_hal_nfc.h +++ b/targets/furi_hal_include/furi_hal_nfc.h @@ -452,6 +452,23 @@ FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity( */ FuriHalNfcError furi_hal_nfc_iso15693_listener_tx_sof(); +/** + * @brief Set FeliCa collision resolution parameters in listener mode. + * + * Configures the NFC hardware for automatic collision resolution. + * + * @param[in] idm pointer to a byte array containing the IDm. + * @param[in] idm_len IDm length in bytes. + * @param[in] pmm pointer to a byte array containing the PMm. + * @param[in] pmm_len PMm length in bytes. + * @returns NfcErrorNone on success, any other error code on failure. +*/ +FuriHalNfcError furi_hal_nfc_felica_listener_set_sensf_res_data( + const uint8_t* idm, + const uint8_t idm_len, + const uint8_t* pmm, + const uint8_t pmm_len); + #ifdef __cplusplus } #endif