Merge branch 'release-candidate' into release
This commit is contained in:
		
						commit
						90eb608f08
					
				
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							| @ -1,2 +1 @@ | |||||||
| * text=auto | * text=auto | ||||||
| 
 |  | ||||||
|  | |||||||
							
								
								
									
										43
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										43
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @ -103,6 +103,32 @@ jobs: | |||||||
|                 -o firmware/.obj/${TARGET}/full.hex -Intel |                 -o firmware/.obj/${TARGET}/full.hex -Intel | ||||||
|             done |             done | ||||||
| 
 | 
 | ||||||
|  |       - name: 'Generate full dfu file' | ||||||
|  |         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||||
|  |         uses: ./.github/actions/docker | ||||||
|  |         with: | ||||||
|  |           run: | | ||||||
|  |             for TARGET in ${TARGETS} | ||||||
|  |             do | ||||||
|  |               hex2dfu \ | ||||||
|  |                 -i firmware/.obj/${TARGET}/full.hex \ | ||||||
|  |                 -o artifacts/flipper-z-${TARGET}-full-${{steps.names.outputs.suffix}}.dfu \ | ||||||
|  |                 -l "Flipper Zero $(echo $TARGET | tr a-z A-Z)" | ||||||
|  |             done | ||||||
|  | 
 | ||||||
|  |       - name: 'Generate full json file' | ||||||
|  |         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||||
|  |         uses: ./.github/actions/docker | ||||||
|  |         with: | ||||||
|  |           run: | | ||||||
|  |             for TARGET in ${TARGETS} | ||||||
|  |             do | ||||||
|  |               jq -s '.[0] * .[1]' \ | ||||||
|  |                 bootloader/.obj/${TARGET}/bootloader.json \ | ||||||
|  |                 firmware/.obj/${TARGET}/firmware.json  \ | ||||||
|  |                 > artifacts/flipper-z-${TARGET}-full-${{steps.names.outputs.suffix}}.json | ||||||
|  |             done | ||||||
|  | 
 | ||||||
|       - name: 'Move upload files' |       - name: 'Move upload files' | ||||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} |         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||||
|         uses: ./.github/actions/docker |         uses: ./.github/actions/docker | ||||||
| @ -116,25 +142,16 @@ jobs: | |||||||
|                 artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.bin |                 artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.bin | ||||||
|               mv bootloader/.obj/${TARGET}/bootloader.elf \ |               mv bootloader/.obj/${TARGET}/bootloader.elf \ | ||||||
|                 artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.elf |                 artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.elf | ||||||
|  |               mv bootloader/.obj/${TARGET}/bootloader.json \ | ||||||
|  |                 artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.json | ||||||
|               mv firmware/.obj/${TARGET}/firmware.dfu \ |               mv firmware/.obj/${TARGET}/firmware.dfu \ | ||||||
|                 artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.dfu |                 artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.dfu | ||||||
|               mv firmware/.obj/${TARGET}/firmware.bin \ |               mv firmware/.obj/${TARGET}/firmware.bin \ | ||||||
|                 artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.bin |                 artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.bin | ||||||
|               mv firmware/.obj/${TARGET}/firmware.elf \ |               mv firmware/.obj/${TARGET}/firmware.elf \ | ||||||
|                 artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.elf |                 artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.elf | ||||||
|             done |               mv firmware/.obj/${TARGET}/firmware.json \ | ||||||
| 
 |                 artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.json | ||||||
|       - name: 'Generate full dfu file' |  | ||||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} |  | ||||||
|         uses: ./.github/actions/docker |  | ||||||
|         with: |  | ||||||
|           run: | |  | ||||||
|             for TARGET in ${TARGETS} |  | ||||||
|             do |  | ||||||
|               hex2dfu \ |  | ||||||
|                 -i firmware/.obj/${TARGET}/full.hex \ |  | ||||||
|                 -o artifacts/flipper-z-${TARGET}-full-${{steps.names.outputs.suffix}}.dfu \ |  | ||||||
|                 -l "Flipper Zero $(echo $TARGET | tr a-z A-Z)" |  | ||||||
|             done |             done | ||||||
| 
 | 
 | ||||||
|       - name: 'Full flash asssembly: bootloader as base' |       - name: 'Full flash asssembly: bootloader as base' | ||||||
|  | |||||||
							
								
								
									
										18
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								Makefile
									
									
									
									
									
								
							| @ -26,6 +26,10 @@ flash: bootloader_flash firmware_flash | |||||||
| debug: | debug: | ||||||
| 	$(MAKE) -C firmware -j$(NPROCS) debug | 	$(MAKE) -C firmware -j$(NPROCS) debug | ||||||
| 
 | 
 | ||||||
|  | .PHONY: blackmagic | ||||||
|  | blackmagic: | ||||||
|  | 	$(MAKE) -C firmware -j$(NPROCS) blackmagic | ||||||
|  | 
 | ||||||
| .PHONY: wipe | .PHONY: wipe | ||||||
| wipe: | wipe: | ||||||
| 	$(PROJECT_ROOT)/scripts/flash.py wipe | 	$(PROJECT_ROOT)/scripts/flash.py wipe | ||||||
| @ -49,12 +53,16 @@ firmware_clean: | |||||||
| 
 | 
 | ||||||
| .PHONY: bootloader_flash | .PHONY: bootloader_flash | ||||||
| bootloader_flash: | bootloader_flash: | ||||||
|  | ifeq ($(FORCE), 1) | ||||||
| 	rm $(PROJECT_ROOT)/bootloader/.obj/f*/flash || true | 	rm $(PROJECT_ROOT)/bootloader/.obj/f*/flash || true | ||||||
|  | endif | ||||||
| 	$(MAKE) -C $(PROJECT_ROOT)/bootloader -j$(NPROCS) flash | 	$(MAKE) -C $(PROJECT_ROOT)/bootloader -j$(NPROCS) flash | ||||||
| 
 | 
 | ||||||
| .PHONY: firmware_flash | .PHONY: firmware_flash | ||||||
| firmware_flash: | firmware_flash: | ||||||
|  | ifeq ($(FORCE), 1) | ||||||
| 	rm $(PROJECT_ROOT)/firmware/.obj/f*/flash || true | 	rm $(PROJECT_ROOT)/firmware/.obj/f*/flash || true | ||||||
|  | endif | ||||||
| 	$(MAKE) -C $(PROJECT_ROOT)/firmware -j$(NPROCS) flash | 	$(MAKE) -C $(PROJECT_ROOT)/firmware -j$(NPROCS) flash | ||||||
| 
 | 
 | ||||||
| .PHONY: flash_radio | .PHONY: flash_radio | ||||||
| @ -73,8 +81,16 @@ flash_radio_fus: | |||||||
| 	@echo "================    JUST DON'T    ================" | 	@echo "================    JUST DON'T    ================" | ||||||
| 	@echo | 	@echo | ||||||
| 
 | 
 | ||||||
| .PHONY:  | .PHONY: flash_radio_fus_please_i_m_not_going_to_complain | ||||||
| flash_radio_fus_please_i_m_not_going_to_complain: | flash_radio_fus_please_i_m_not_going_to_complain: | ||||||
| 	$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOOSE_FLIPPER_FEATURES_THAT_USES_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw_for_fus_0_5_3.bin | 	$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOOSE_FLIPPER_FEATURES_THAT_USES_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw_for_fus_0_5_3.bin | ||||||
| 	$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOOSE_FLIPPER_FEATURES_THAT_USES_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw.bin | 	$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOOSE_FLIPPER_FEATURES_THAT_USES_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw.bin | ||||||
| 	$(PROJECT_ROOT)/scripts/ob.py set | 	$(PROJECT_ROOT)/scripts/ob.py set | ||||||
|  | 
 | ||||||
|  | FORMAT_SOURCES = $(shell find applications bootloader core -iname "*.h" -o -iname "*.c" -o -iname "*.cpp") | ||||||
|  | 
 | ||||||
|  | .PHONY: format | ||||||
|  | format: | ||||||
|  | 	@echo "Formatting sources with clang-format" | ||||||
|  | 	@clang-format -style=file -i $(FORMAT_SOURCES) | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -129,11 +129,11 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static DialogMessageButton boot_version_screen(DialogsApp* dialogs, DialogMessage* message) { | static DialogMessageButton bootloader_version_screen(DialogsApp* dialogs, DialogMessage* message) { | ||||||
|     DialogMessageButton result; |     DialogMessageButton result; | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     string_init(buffer); |     string_init(buffer); | ||||||
|     const Version* ver = furi_hal_version_get_boot_version(); |     const Version* ver = furi_hal_version_get_bootloader_version(); | ||||||
| 
 | 
 | ||||||
|     if(!ver) { |     if(!ver) { | ||||||
|         string_cat_printf(buffer, "No info\n"); |         string_cat_printf(buffer, "No info\n"); | ||||||
| @ -167,7 +167,7 @@ const AboutDialogScreen about_screens[] = { | |||||||
|     icon2_screen, |     icon2_screen, | ||||||
|     hw_version_screen, |     hw_version_screen, | ||||||
|     fw_version_screen, |     fw_version_screen, | ||||||
|     boot_version_screen}; |     bootloader_version_screen}; | ||||||
| 
 | 
 | ||||||
| const size_t about_screens_count = sizeof(about_screens) / sizeof(AboutDialogScreen); | const size_t about_screens_count = sizeof(about_screens) / sizeof(AboutDialogScreen); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -65,55 +65,55 @@ extern int32_t power_settings_app(void* p); | |||||||
| const FlipperApplication FLIPPER_SERVICES[] = { | const FlipperApplication FLIPPER_SERVICES[] = { | ||||||
| /* Services */ | /* Services */ | ||||||
| #ifdef SRV_RPC | #ifdef SRV_RPC | ||||||
|     {.app = rpc_srv, .name = "RPC", .stack_size = 1024 * 4, .icon = NULL}, |     {.app = rpc_srv, .name = "RpcSrv", .stack_size = 1024 * 4, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_BT | #ifdef SRV_BT | ||||||
|     {.app = bt_srv, .name = "BT", .stack_size = 1024, .icon = NULL}, |     {.app = bt_srv, .name = "BtSrv", .stack_size = 1024, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_CLI | #ifdef SRV_CLI | ||||||
|     {.app = cli_srv, .name = "Cli", .stack_size = 4096, .icon = NULL}, |     {.app = cli_srv, .name = "CliSrv", .stack_size = 4096, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_DIALOGS | #ifdef SRV_DIALOGS | ||||||
|     {.app = dialogs_srv, .name = "Dialogs", .stack_size = 1024, .icon = NULL}, |     {.app = dialogs_srv, .name = "DialogsSrv", .stack_size = 1024, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_DOLPHIN | #ifdef SRV_DOLPHIN | ||||||
|     {.app = dolphin_srv, .name = "Dolphin", .stack_size = 1024, .icon = NULL}, |     {.app = dolphin_srv, .name = "DolphinSrv", .stack_size = 1024, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_DESKTOP | #ifdef SRV_DESKTOP | ||||||
|     {.app = desktop_srv, .name = "Desktop", .stack_size = 1024, .icon = NULL}, |     {.app = desktop_srv, .name = "DesktopSrv", .stack_size = 2048, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_GUI | #ifdef SRV_GUI | ||||||
|     {.app = gui_srv, .name = "Gui", .stack_size = 8192, .icon = NULL}, |     {.app = gui_srv, .name = "GuiSrv", .stack_size = 2048, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_INPUT | #ifdef SRV_INPUT | ||||||
|     {.app = input_srv, .name = "Input", .stack_size = 1024, .icon = NULL}, |     {.app = input_srv, .name = "InputSrv", .stack_size = 1024, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_LOADER | #ifdef SRV_LOADER | ||||||
|     {.app = loader_srv, .name = "Loader", .stack_size = 1024, .icon = NULL}, |     {.app = loader_srv, .name = "LoaderSrv", .stack_size = 1024, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_NOTIFICATION | #ifdef SRV_NOTIFICATION | ||||||
|     {.app = notification_srv, .name = "Notification", .stack_size = 1024, .icon = NULL}, |     {.app = notification_srv, .name = "NotificationSrv", .stack_size = 1536, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_POWER | #ifdef SRV_POWER | ||||||
|     {.app = power_srv, .name = "Power", .stack_size = 1024, .icon = NULL}, |     {.app = power_srv, .name = "PowerSrv", .stack_size = 1024, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_POWER_OBSERVER | #ifdef SRV_POWER_OBSERVER | ||||||
|     {.app = power_observer_srv, .name = "PowerObserver", .stack_size = 1024, .icon = NULL}, |     {.app = power_observer_srv, .name = "PowerAuditSrv", .stack_size = 1024, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_STORAGE | #ifdef SRV_STORAGE | ||||||
|     {.app = storage_srv, .name = "Storage", .stack_size = 4096, .icon = NULL}, |     {.app = storage_srv, .name = "StorageSrv", .stack_size = 3072, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -231,6 +231,10 @@ endif | |||||||
| SRV_RPC ?= 0 | SRV_RPC ?= 0 | ||||||
| ifeq ($(SRV_RPC), 1) | ifeq ($(SRV_RPC), 1) | ||||||
| CFLAGS		+= -DSRV_RPC | CFLAGS		+= -DSRV_RPC | ||||||
|  | ifeq ($(SRV_RPC_DEBUG), 1) | ||||||
|  | CFLAGS		+= -DSRV_RPC_DEBUG | ||||||
|  | endif | ||||||
|  | SRV_CLI		= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| SRV_LOADER ?= 0 | SRV_LOADER ?= 0 | ||||||
|  | |||||||
| @ -104,7 +104,7 @@ void archive_file_array_swap(ArchiveBrowserView* browser, int8_t d) { | |||||||
| void archive_file_array_rm_all(ArchiveBrowserView* browser) { | void archive_file_array_rm_all(ArchiveBrowserView* browser) { | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         browser->view, (ArchiveBrowserViewModel * model) { |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|             files_array_clean(model->files); |             files_array_reset(model->files); | ||||||
|             return false; |             return false; | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
|  | |||||||
| @ -53,7 +53,7 @@ bool archive_favorites_read(void* context) { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             archive_add_item(browser, &file_info, string_get_cstr(buffer)); |             archive_add_item(browser, &file_info, string_get_cstr(buffer)); | ||||||
|             string_clean(buffer); |             string_reset(buffer); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     string_clear(buffer); |     string_clear(buffer); | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| #include "archive_files.h" | #include "archive_files.h" | ||||||
| #include "archive_browser.h" | #include "archive_browser.h" | ||||||
| 
 | 
 | ||||||
|  | #define TAG "Archive" | ||||||
|  | 
 | ||||||
| bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { | bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { | ||||||
|     furi_assert(file_info); |     furi_assert(file_info); | ||||||
|     furi_assert(tab_ext); |     furi_assert(tab_ext); | ||||||
| @ -147,11 +149,11 @@ void archive_file_append(const char* path, const char* format, ...) { | |||||||
|     FileWorker* file_worker = file_worker_alloc(false); |     FileWorker* file_worker = file_worker_alloc(false); | ||||||
| 
 | 
 | ||||||
|     if(!file_worker_open(file_worker, path, FSAM_WRITE, FSOM_OPEN_APPEND)) { |     if(!file_worker_open(file_worker, path, FSAM_WRITE, FSOM_OPEN_APPEND)) { | ||||||
|         FURI_LOG_E("Archive", "Append open error"); |         FURI_LOG_E(TAG, "Append open error"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(!file_worker_write(file_worker, string_get_cstr(string), string_size(string))) { |     if(!file_worker_write(file_worker, string_get_cstr(string), string_size(string))) { | ||||||
|         FURI_LOG_E("Archive", "Append write error"); |         FURI_LOG_E(TAG, "Append write error"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     file_worker_close(file_worker); |     file_worker_close(file_worker); | ||||||
|  | |||||||
| @ -1,7 +1,8 @@ | |||||||
| #include "bt_i.h" | #include "bt_i.h" | ||||||
| #include "battery_service.h" | #include "battery_service.h" | ||||||
|  | #include "bt_keys_storage.h" | ||||||
| 
 | 
 | ||||||
| #define BT_SERVICE_TAG "BT" | #define TAG "BtSrv" | ||||||
| 
 | 
 | ||||||
| static void bt_draw_statusbar_callback(Canvas* canvas, void* context) { | static void bt_draw_statusbar_callback(Canvas* canvas, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| @ -69,8 +70,8 @@ Bt* bt_alloc() { | |||||||
| 
 | 
 | ||||||
|     // Power
 |     // Power
 | ||||||
|     bt->power = furi_record_open("power"); |     bt->power = furi_record_open("power"); | ||||||
|     PubSub* power_pubsub = power_get_pubsub(bt->power); |     FuriPubSub* power_pubsub = power_get_pubsub(bt->power); | ||||||
|     subscribe_pubsub(power_pubsub, bt_battery_level_changed_callback, bt); |     furi_pubsub_subscribe(power_pubsub, bt_battery_level_changed_callback, bt); | ||||||
| 
 | 
 | ||||||
|     // RPC
 |     // RPC
 | ||||||
|     bt->rpc = furi_record_open("rpc"); |     bt->rpc = furi_record_open("rpc"); | ||||||
| @ -80,14 +81,15 @@ Bt* bt_alloc() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Called from GAP thread from Serial service
 | // Called from GAP thread from Serial service
 | ||||||
| static void bt_on_data_received_callback(uint8_t* data, uint16_t size, void* context) { | static uint16_t bt_on_data_received_callback(uint8_t* data, uint16_t size, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     Bt* bt = context; |     Bt* bt = context; | ||||||
| 
 | 
 | ||||||
|     size_t bytes_processed = rpc_feed_bytes(bt->rpc_session, data, size, 1000); |     size_t bytes_processed = rpc_session_feed(bt->rpc_session, data, size, 1000); | ||||||
|     if(bytes_processed != size) { |     if(bytes_processed != size) { | ||||||
|         FURI_LOG_E(BT_SERVICE_TAG, "Only %d of %d bytes processed by RPC", bytes_processed, size); |         FURI_LOG_E(TAG, "Only %d of %d bytes processed by RPC", bytes_processed, size); | ||||||
|     } |     } | ||||||
|  |     return rpc_session_get_available_size(bt->rpc_session); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Called from GAP thread from Serial service
 | // Called from GAP thread from Serial service
 | ||||||
| @ -117,6 +119,11 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void bt_rpc_buffer_is_empty_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     furi_hal_bt_notify_buffer_is_empty(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Called from GAP thread
 | // Called from GAP thread
 | ||||||
| static void bt_on_gap_event_callback(BleEvent event, void* context) { | static void bt_on_gap_event_callback(BleEvent event, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| @ -128,11 +135,13 @@ static void bt_on_gap_event_callback(BleEvent event, void* context) { | |||||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; |         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|         // Open RPC session
 |         // Open RPC session
 | ||||||
|         FURI_LOG_I(BT_SERVICE_TAG, "Open RPC connection"); |         FURI_LOG_I(TAG, "Open RPC connection"); | ||||||
|         bt->rpc_session = rpc_open_session(bt->rpc); |         bt->rpc_session = rpc_session_open(bt->rpc); | ||||||
|         rpc_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback, bt); |         rpc_session_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback); | ||||||
|  |         rpc_session_set_buffer_is_empty_callback(bt->rpc_session, bt_rpc_buffer_is_empty_callback); | ||||||
|  |         rpc_session_set_context(bt->rpc_session, bt); | ||||||
|         furi_hal_bt_set_data_event_callbacks( |         furi_hal_bt_set_data_event_callbacks( | ||||||
|             bt_on_data_received_callback, bt_on_data_sent_callback, bt); |             RPC_BUFFER_SIZE, bt_on_data_received_callback, bt_on_data_sent_callback, bt); | ||||||
|         // Update battery level
 |         // Update battery level
 | ||||||
|         PowerInfo info; |         PowerInfo info; | ||||||
|         power_get_info(bt->power, &info); |         power_get_info(bt->power, &info); | ||||||
| @ -140,9 +149,9 @@ static void bt_on_gap_event_callback(BleEvent event, void* context) { | |||||||
|         message.data.battery_level = info.charge; |         message.data.battery_level = info.charge; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|     } else if(event.type == BleEventTypeDisconnected) { |     } else if(event.type == BleEventTypeDisconnected) { | ||||||
|         FURI_LOG_I(BT_SERVICE_TAG, "Close RPC connection"); |         FURI_LOG_I(TAG, "Close RPC connection"); | ||||||
|         if(bt->rpc_session) { |         if(bt->rpc_session) { | ||||||
|             rpc_close_session(bt->rpc_session); |             rpc_session_close(bt->rpc_session); | ||||||
|             bt->rpc_session = NULL; |             bt->rpc_session = NULL; | ||||||
|         } |         } | ||||||
|     } else if(event.type == BleEventTypeStartAdvertising) { |     } else if(event.type == BleEventTypeStartAdvertising) { | ||||||
| @ -160,6 +169,14 @@ static void bt_on_gap_event_callback(BleEvent event, void* context) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Bt* bt = context; | ||||||
|  |     FURI_LOG_I(TAG, "Changed addr start: %08lX, size changed: %d", addr, size); | ||||||
|  |     BtMessage message = {.type = BtMessageTypeKeysStorageUpdated}; | ||||||
|  |     furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void bt_statusbar_update(Bt* bt) { | static void bt_statusbar_update(Bt* bt) { | ||||||
|     if(bt->status == BtStatusAdvertising) { |     if(bt->status == BtStatusAdvertising) { | ||||||
|         view_port_set_width(bt->statusbar_view_port, icon_get_width(&I_Bluetooth_5x8)); |         view_port_set_width(bt->statusbar_view_port, icon_get_width(&I_Bluetooth_5x8)); | ||||||
| @ -176,19 +193,26 @@ int32_t bt_srv() { | |||||||
|     Bt* bt = bt_alloc(); |     Bt* bt = bt_alloc(); | ||||||
|     furi_record_create("bt", bt); |     furi_record_create("bt", bt); | ||||||
| 
 | 
 | ||||||
|     if(!furi_hal_bt_wait_startup()) { |     // Read keys
 | ||||||
|         FURI_LOG_E(BT_SERVICE_TAG, "Core2 startup failed"); |     if(!bt_load_key_storage(bt)) { | ||||||
|  |         FURI_LOG_W(TAG, "Failed to load saved bonding keys"); | ||||||
|  |     } | ||||||
|  |     // Start 2nd core
 | ||||||
|  |     if(!furi_hal_bt_start_core2()) { | ||||||
|  |         FURI_LOG_E(TAG, "Core2 startup failed"); | ||||||
|     } else { |     } else { | ||||||
|         view_port_enabled_set(bt->statusbar_view_port, true); |         view_port_enabled_set(bt->statusbar_view_port, true); | ||||||
|         if(furi_hal_bt_init_app(bt_on_gap_event_callback, bt)) { |         if(furi_hal_bt_init_app(bt_on_gap_event_callback, bt)) { | ||||||
|             FURI_LOG_I(BT_SERVICE_TAG, "BLE stack started"); |             FURI_LOG_I(TAG, "BLE stack started"); | ||||||
|             if(bt->bt_settings.enabled) { |             if(bt->bt_settings.enabled) { | ||||||
|                 furi_hal_bt_start_advertising(); |                 furi_hal_bt_start_advertising(); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             FURI_LOG_E(BT_SERVICE_TAG, "BT App start failed"); |             FURI_LOG_E(TAG, "BT App start failed"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); | ||||||
|  | 
 | ||||||
|     // Update statusbar
 |     // Update statusbar
 | ||||||
|     bt_statusbar_update(bt); |     bt_statusbar_update(bt); | ||||||
| 
 | 
 | ||||||
| @ -206,6 +230,8 @@ int32_t bt_srv() { | |||||||
|         } else if(message.type == BtMessageTypePinCodeShow) { |         } else if(message.type == BtMessageTypePinCodeShow) { | ||||||
|             // Display PIN code
 |             // Display PIN code
 | ||||||
|             bt_pin_code_show_event_handler(bt, message.data.pin_code); |             bt_pin_code_show_event_handler(bt, message.data.pin_code); | ||||||
|  |         } else if(message.type == BtMessageTypeKeysStorageUpdated) { | ||||||
|  |             bt_save_key_storage(bt); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     return 0; |     return 0; | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ typedef enum { | |||||||
|     BtMessageTypeUpdateStatusbar, |     BtMessageTypeUpdateStatusbar, | ||||||
|     BtMessageTypeUpdateBatteryLevel, |     BtMessageTypeUpdateBatteryLevel, | ||||||
|     BtMessageTypePinCodeShow, |     BtMessageTypePinCodeShow, | ||||||
|  |     BtMessageTypeKeysStorageUpdated, | ||||||
| } BtMessageType; | } BtMessageType; | ||||||
| 
 | 
 | ||||||
| typedef union { | typedef union { | ||||||
| @ -38,6 +39,8 @@ typedef struct { | |||||||
| } BtMessage; | } BtMessage; | ||||||
| 
 | 
 | ||||||
| struct Bt { | struct Bt { | ||||||
|  |     uint8_t* bt_keys_addr_start; | ||||||
|  |     uint16_t bt_keys_size; | ||||||
|     BtSettings bt_settings; |     BtSettings bt_settings; | ||||||
|     BtStatus status; |     BtStatus status; | ||||||
|     osMessageQueueId_t message_queue; |     osMessageQueueId_t message_queue; | ||||||
|  | |||||||
							
								
								
									
										41
									
								
								applications/bt/bt_service/bt_keys_storage.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								applications/bt/bt_service/bt_keys_storage.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | |||||||
|  | #include "bt_keys_storage.h" | ||||||
|  | #include <furi.h> | ||||||
|  | #include <file-worker.h> | ||||||
|  | 
 | ||||||
|  | #define BT_KEYS_STORAGE_TAG "bt keys storage" | ||||||
|  | #define BT_KEYS_STORAGE_PATH "/int/bt.keys" | ||||||
|  | 
 | ||||||
|  | bool bt_load_key_storage(Bt* bt) { | ||||||
|  |     furi_assert(bt); | ||||||
|  | 
 | ||||||
|  |     bool file_loaded = false; | ||||||
|  |     furi_hal_bt_get_key_storage_buff(&bt->bt_keys_addr_start, &bt->bt_keys_size); | ||||||
|  | 
 | ||||||
|  |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
|  |     if(file_worker_open(file_worker, BT_KEYS_STORAGE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||||
|  |         furi_hal_bt_nvm_sram_sem_acquire(); | ||||||
|  |         if(file_worker_read(file_worker, bt->bt_keys_addr_start, bt->bt_keys_size)) { | ||||||
|  |             file_loaded = true; | ||||||
|  |         } | ||||||
|  |         furi_hal_bt_nvm_sram_sem_release(); | ||||||
|  |     } | ||||||
|  |     file_worker_free(file_worker); | ||||||
|  |     return file_loaded; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool bt_save_key_storage(Bt* bt) { | ||||||
|  |     furi_assert(bt); | ||||||
|  |     furi_assert(bt->bt_keys_addr_start); | ||||||
|  | 
 | ||||||
|  |     bool file_saved = false; | ||||||
|  |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
|  |     if(file_worker_open(file_worker, BT_KEYS_STORAGE_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) { | ||||||
|  |         furi_hal_bt_nvm_sram_sem_acquire(); | ||||||
|  |         if(file_worker_write(file_worker, bt->bt_keys_addr_start, bt->bt_keys_size)) { | ||||||
|  |             file_saved = true; | ||||||
|  |         } | ||||||
|  |         furi_hal_bt_nvm_sram_sem_release(); | ||||||
|  |     } | ||||||
|  |     file_worker_free(file_worker); | ||||||
|  |     return file_saved; | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								applications/bt/bt_service/bt_keys_storage.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								applications/bt/bt_service/bt_keys_storage.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "bt_i.h" | ||||||
|  | 
 | ||||||
|  | bool bt_load_key_storage(Bt* bt); | ||||||
|  | 
 | ||||||
|  | bool bt_save_key_storage(Bt* bt); | ||||||
| @ -2,7 +2,7 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <file-worker.h> | #include <file-worker.h> | ||||||
| 
 | 
 | ||||||
| #define BT_SETTINGS_TAG "bt settings" | #define TAG "BtSettings" | ||||||
| #define BT_SETTINGS_PATH "/int/bt.settings" | #define BT_SETTINGS_PATH "/int/bt.settings" | ||||||
| 
 | 
 | ||||||
| bool bt_settings_load(BtSettings* bt_settings) { | bool bt_settings_load(BtSettings* bt_settings) { | ||||||
| @ -10,7 +10,7 @@ bool bt_settings_load(BtSettings* bt_settings) { | |||||||
|     bool file_loaded = false; |     bool file_loaded = false; | ||||||
|     BtSettings settings = {}; |     BtSettings settings = {}; | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(BT_SETTINGS_TAG, "Loading settings from \"%s\"", BT_SETTINGS_PATH); |     FURI_LOG_I(TAG, "Loading settings from \"%s\"", BT_SETTINGS_PATH); | ||||||
|     FileWorker* file_worker = file_worker_alloc(true); |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
|     if(file_worker_open(file_worker, BT_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { |     if(file_worker_open(file_worker, BT_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||||
|         if(file_worker_read(file_worker, &settings, sizeof(settings))) { |         if(file_worker_read(file_worker, &settings, sizeof(settings))) { | ||||||
| @ -20,16 +20,16 @@ bool bt_settings_load(BtSettings* bt_settings) { | |||||||
|     file_worker_free(file_worker); |     file_worker_free(file_worker); | ||||||
| 
 | 
 | ||||||
|     if(file_loaded) { |     if(file_loaded) { | ||||||
|         FURI_LOG_I(BT_SETTINGS_TAG, "Settings load success"); |         FURI_LOG_I(TAG, "Settings load success"); | ||||||
|         if(settings.version != BT_SETTINGS_VERSION) { |         if(settings.version != BT_SETTINGS_VERSION) { | ||||||
|             FURI_LOG_E(BT_SETTINGS_TAG, "Settings version mismatch"); |             FURI_LOG_E(TAG, "Settings version mismatch"); | ||||||
|         } else { |         } else { | ||||||
|             osKernelLock(); |             osKernelLock(); | ||||||
|             *bt_settings = settings; |             *bt_settings = settings; | ||||||
|             osKernelUnlock(); |             osKernelUnlock(); | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|         FURI_LOG_E(BT_SETTINGS_TAG, "Settings load failed"); |         FURI_LOG_E(TAG, "Settings load failed"); | ||||||
|     } |     } | ||||||
|     return file_loaded; |     return file_loaded; | ||||||
| } | } | ||||||
| @ -41,7 +41,7 @@ bool bt_settings_save(BtSettings* bt_settings) { | |||||||
|     FileWorker* file_worker = file_worker_alloc(true); |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
|     if(file_worker_open(file_worker, BT_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) { |     if(file_worker_open(file_worker, BT_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) { | ||||||
|         if(file_worker_write(file_worker, bt_settings, sizeof(BtSettings))) { |         if(file_worker_write(file_worker, bt_settings, sizeof(BtSettings))) { | ||||||
|             FURI_LOG_I(BT_SETTINGS_TAG, "Settings saved to \"%s\"", BT_SETTINGS_PATH); |             FURI_LOG_I(TAG, "Settings saved to \"%s\"", BT_SETTINGS_PATH); | ||||||
|             result = true; |             result = true; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -263,7 +263,7 @@ static void cli_handle_autocomplete(Cli* cli) { | |||||||
|         cli->cursor_position = string_size(cli->line); |         cli->cursor_position = string_size(cli->line); | ||||||
|     } |     } | ||||||
|     // Cleanup
 |     // Cleanup
 | ||||||
|     string_clean(common); |     string_clear(common); | ||||||
|     // Show prompt
 |     // Show prompt
 | ||||||
|     cli_prompt(cli); |     cli_prompt(cli); | ||||||
| } | } | ||||||
|  | |||||||
| @ -56,7 +56,7 @@ static const uint8_t enclave_signature_expected[ENCLAVE_SIGNATURE_KEY_SLOTS][ENC | |||||||
|  */ |  */ | ||||||
| void cli_command_device_info(Cli* cli, string_t args, void* context) { | void cli_command_device_info(Cli* cli, string_t args, void* context) { | ||||||
|     // Device Info version
 |     // Device Info version
 | ||||||
|     printf("device_info_major   : %d\r\n", 1); |     printf("device_info_major       : %d\r\n", 2); | ||||||
|     printf("device_info_minor       : %d\r\n", 0); |     printf("device_info_minor       : %d\r\n", 0); | ||||||
|     // Model name
 |     // Model name
 | ||||||
|     printf("hardware_model          : %s\r\n", furi_hal_version_get_model_name()); |     printf("hardware_model          : %s\r\n", furi_hal_version_get_model_name()); | ||||||
| @ -89,14 +89,14 @@ void cli_command_device_info(Cli* cli, string_t args, void* context) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Bootloader Version
 |     // Bootloader Version
 | ||||||
|     const Version* boot_version = furi_hal_version_get_boot_version(); |     const Version* bootloader_version = furi_hal_version_get_bootloader_version(); | ||||||
|     if(boot_version) { |     if(bootloader_version) { | ||||||
|         printf("boot_commit         : %s\r\n", version_get_githash(boot_version)); |         printf("bootloader_commit       : %s\r\n", version_get_githash(bootloader_version)); | ||||||
|         printf("boot_branch         : %s\r\n", version_get_gitbranch(boot_version)); |         printf("bootloader_branch       : %s\r\n", version_get_gitbranch(bootloader_version)); | ||||||
|         printf("boot_branch_num     : %s\r\n", version_get_gitbranchnum(boot_version)); |         printf("bootloader_branch_num   : %s\r\n", version_get_gitbranchnum(bootloader_version)); | ||||||
|         printf("boot_version        : %s\r\n", version_get_version(boot_version)); |         printf("bootloader_version      : %s\r\n", version_get_version(bootloader_version)); | ||||||
|         printf("boot_build_date     : %s\r\n", version_get_builddate(boot_version)); |         printf("bootloader_build_date   : %s\r\n", version_get_builddate(bootloader_version)); | ||||||
|         printf("boot_target         : %d\r\n", version_get_target(boot_version)); |         printf("bootloader_target       : %d\r\n", version_get_target(bootloader_version)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Firmware version
 |     // Firmware version
 | ||||||
| @ -350,7 +350,7 @@ void cli_command_gpio_set(Cli* cli, string_t args, void* context) { | |||||||
|         "PA4", |         "PA4", | ||||||
|         "PA6", |         "PA6", | ||||||
|         "PA7", |         "PA7", | ||||||
| #ifdef DEBUG | #ifdef FURI_DEBUG | ||||||
|         "PA0", |         "PA0", | ||||||
|         "PB7", |         "PB7", | ||||||
|         "PB8", |         "PB8", | ||||||
| @ -366,7 +366,7 @@ void cli_command_gpio_set(Cli* cli, string_t args, void* context) { | |||||||
|         {.port = GPIOA, .pin = LL_GPIO_PIN_4}, |         {.port = GPIOA, .pin = LL_GPIO_PIN_4}, | ||||||
|         {.port = GPIOA, .pin = LL_GPIO_PIN_6}, |         {.port = GPIOA, .pin = LL_GPIO_PIN_6}, | ||||||
|         {.port = GPIOA, .pin = LL_GPIO_PIN_7}, |         {.port = GPIOA, .pin = LL_GPIO_PIN_7}, | ||||||
| #ifdef DEBUG | #ifdef FURI_DEBUG | ||||||
|         {.port = GPIOA, .pin = LL_GPIO_PIN_0}, // IR_RX (PA0)
 |         {.port = GPIOA, .pin = LL_GPIO_PIN_0}, // IR_RX (PA0)
 | ||||||
|         {.port = GPIOB, .pin = LL_GPIO_PIN_7}, // UART RX (PB7)
 |         {.port = GPIOB, .pin = LL_GPIO_PIN_7}, // UART RX (PB7)
 | ||||||
|         {.port = GPIOB, .pin = LL_GPIO_PIN_8}, // SPEAKER (PB8)
 |         {.port = GPIOB, .pin = LL_GPIO_PIN_8}, // SPEAKER (PB8)
 | ||||||
| @ -411,7 +411,7 @@ void cli_command_gpio_set(Cli* cli, string_t args, void* context) { | |||||||
|         LL_GPIO_SetPinOutputType(gpio[num].port, gpio[num].pin, LL_GPIO_OUTPUT_PUSHPULL); |         LL_GPIO_SetPinOutputType(gpio[num].port, gpio[num].pin, LL_GPIO_OUTPUT_PUSHPULL); | ||||||
|         LL_GPIO_ResetOutputPin(gpio[num].port, gpio[num].pin); |         LL_GPIO_ResetOutputPin(gpio[num].port, gpio[num].pin); | ||||||
|     } else if(!string_cmp(args, "1")) { |     } else if(!string_cmp(args, "1")) { | ||||||
| #ifdef DEBUG | #ifdef FURI_DEBUG | ||||||
|         if(num == 8) { // PA0
 |         if(num == 8) { // PA0
 | ||||||
|             printf( |             printf( | ||||||
|                 "Setting PA0 pin HIGH with TSOP connected can damage IR receiver. Are you sure you want to continue? (y/n)?\r\n"); |                 "Setting PA0 pin HIGH with TSOP connected can damage IR receiver. Are you sure you want to continue? (y/n)?\r\n"); | ||||||
|  | |||||||
| @ -6,6 +6,9 @@ | |||||||
| #include <furi-hal-usb-hid.h> | #include <furi-hal-usb-hid.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| 
 | 
 | ||||||
|  | #define TAG "BadUsb" | ||||||
|  | #define WORKER_TAG TAG "Worker" | ||||||
|  | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     EventTypeInput, |     EventTypeInput, | ||||||
|     EventTypeWorkerState, |     EventTypeWorkerState, | ||||||
| @ -191,7 +194,7 @@ static bool ducky_parse_line(string_t line, BadUsbParams* app) { | |||||||
| 
 | 
 | ||||||
| static void badusb_worker(void* context) { | static void badusb_worker(void* context) { | ||||||
|     BadUsbParams* app = context; |     BadUsbParams* app = context; | ||||||
|     FURI_LOG_I("BadUSB worker", "Init"); |     FURI_LOG_I(WORKER_TAG, "Init"); | ||||||
|     File* script_file = storage_file_alloc(furi_record_open("storage")); |     File* script_file = storage_file_alloc(furi_record_open("storage")); | ||||||
|     BadUsbEvent evt; |     BadUsbEvent evt; | ||||||
|     string_t line; |     string_t line; | ||||||
| @ -203,7 +206,7 @@ static void badusb_worker(void* context) { | |||||||
|         uint32_t flags = |         uint32_t flags = | ||||||
|             osThreadFlagsWait(WorkerCmdStart | WorkerCmdStop, osFlagsWaitAny, osWaitForever); |             osThreadFlagsWait(WorkerCmdStart | WorkerCmdStop, osFlagsWaitAny, osWaitForever); | ||||||
|         if(flags & WorkerCmdStart) { |         if(flags & WorkerCmdStart) { | ||||||
|             FURI_LOG_I("BadUSB worker", "Start"); |             FURI_LOG_I(WORKER_TAG, "Start"); | ||||||
|             do { |             do { | ||||||
|                 ret = storage_file_read(script_file, buffer, 16); |                 ret = storage_file_read(script_file, buffer, 16); | ||||||
|                 for(uint16_t i = 0; i < ret; i++) { |                 for(uint16_t i = 0; i < ret; i++) { | ||||||
| @ -211,7 +214,7 @@ static void badusb_worker(void* context) { | |||||||
|                         line_cnt++; |                         line_cnt++; | ||||||
|                         if(ducky_parse_line(line, app) == false) { |                         if(ducky_parse_line(line, app) == false) { | ||||||
|                             ret = 0; |                             ret = 0; | ||||||
|                             FURI_LOG_E("BadUSB worker", "Unknown command at line %lu", line_cnt); |                             FURI_LOG_E(WORKER_TAG, "Unknown command at line %lu", line_cnt); | ||||||
|                             evt.type = EventTypeWorkerState; |                             evt.type = EventTypeWorkerState; | ||||||
|                             evt.worker.state = WorkerStateScriptError; |                             evt.worker.state = WorkerStateScriptError; | ||||||
|                             evt.worker.line = line_cnt; |                             evt.worker.line = line_cnt; | ||||||
| @ -223,7 +226,7 @@ static void badusb_worker(void* context) { | |||||||
|                             ret = 0; |                             ret = 0; | ||||||
|                             break; |                             break; | ||||||
|                         } |                         } | ||||||
|                         string_clean(line); |                         string_reset(line); | ||||||
|                     } else { |                     } else { | ||||||
|                         string_push_back(line, buffer[i]); |                         string_push_back(line, buffer[i]); | ||||||
|                     } |                     } | ||||||
| @ -231,19 +234,19 @@ static void badusb_worker(void* context) { | |||||||
|             } while(ret > 0); |             } while(ret > 0); | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|         FURI_LOG_E("BadUSB worker", "Script file open error"); |         FURI_LOG_E(WORKER_TAG, "Script file open error"); | ||||||
|         evt.type = EventTypeWorkerState; |         evt.type = EventTypeWorkerState; | ||||||
|         evt.worker.state = WorkerStateNoFile; |         evt.worker.state = WorkerStateNoFile; | ||||||
|         osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever); |         osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever); | ||||||
|     } |     } | ||||||
|     string_clean(line); |     string_reset(line); | ||||||
|     string_clear(line); |     string_clear(line); | ||||||
| 
 | 
 | ||||||
|     furi_hal_hid_kb_release_all(); |     furi_hal_hid_kb_release_all(); | ||||||
|     storage_file_close(script_file); |     storage_file_close(script_file); | ||||||
|     storage_file_free(script_file); |     storage_file_free(script_file); | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I("BadUSB worker", "End"); |     FURI_LOG_I(WORKER_TAG, "End"); | ||||||
|     evt.type = EventTypeWorkerState; |     evt.type = EventTypeWorkerState; | ||||||
|     evt.worker.state = WorkerStateDone; |     evt.worker.state = WorkerStateDone; | ||||||
|     osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever); |     osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever); | ||||||
| @ -324,7 +327,7 @@ int32_t bad_usb_app(void* p) { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } else if(event.type == EventTypeWorkerState) { |             } else if(event.type == EventTypeWorkerState) { | ||||||
|                 FURI_LOG_I("BadUSB app", "ev: %d", event.worker.state); |                 FURI_LOG_I(TAG, "ev: %d", event.worker.state); | ||||||
|                 if(event.worker.state == WorkerStateDone) { |                 if(event.worker.state == WorkerStateDone) { | ||||||
|                     worker_running = false; |                     worker_running = false; | ||||||
|                     if(app_state == AppStateExit) |                     if(app_state == AppStateExit) | ||||||
|  | |||||||
| @ -14,6 +14,8 @@ | |||||||
| 
 | 
 | ||||||
| #include "view_display_test.h" | #include "view_display_test.h" | ||||||
| 
 | 
 | ||||||
|  | #define TAG "DisplayTest" | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     Gui* gui; |     Gui* gui; | ||||||
|     ViewDispatcher* view_dispatcher; |     ViewDispatcher* view_dispatcher; | ||||||
| @ -77,7 +79,7 @@ static uint32_t display_test_exit_callback(void* context) { | |||||||
| 
 | 
 | ||||||
| static void display_test_reload_config(DisplayTest* instance) { | static void display_test_reload_config(DisplayTest* instance) { | ||||||
|     FURI_LOG_I( |     FURI_LOG_I( | ||||||
|         "DisplayTest", |         TAG, | ||||||
|         "contrast: %d, regulation_ratio: %d, bias: %d", |         "contrast: %d, regulation_ratio: %d, bias: %d", | ||||||
|         instance->config_contrast, |         instance->config_contrast, | ||||||
|         instance->config_regulation_ratio, |         instance->config_regulation_ratio, | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ | |||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <input/input.h> | #include <input/input.h> | ||||||
| 
 | 
 | ||||||
|  | #define TAG "KeypadTest" | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     bool press[5]; |     bool press[5]; | ||||||
|     uint16_t up; |     uint16_t up; | ||||||
| @ -80,7 +82,7 @@ int32_t keypad_test_app(void* p) { | |||||||
| 
 | 
 | ||||||
|     ValueMutex state_mutex; |     ValueMutex state_mutex; | ||||||
|     if(!init_mutex(&state_mutex, &_state, sizeof(KeypadTestState))) { |     if(!init_mutex(&state_mutex, &_state, sizeof(KeypadTestState))) { | ||||||
|         FURI_LOG_E("KeypadTest", "cannot create mutex"); |         FURI_LOG_E(TAG, "cannot create mutex"); | ||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -101,7 +103,7 @@ int32_t keypad_test_app(void* p) { | |||||||
|         if(event_status == osOK) { |         if(event_status == osOK) { | ||||||
|             if(event.type == EventTypeInput) { |             if(event.type == EventTypeInput) { | ||||||
|                 FURI_LOG_I( |                 FURI_LOG_I( | ||||||
|                     "KeypadTest", |                     TAG, | ||||||
|                     "key: %s type: %s", |                     "key: %s type: %s", | ||||||
|                     input_get_key_name(event.input.key), |                     input_get_key_name(event.input.key), | ||||||
|                     input_get_type_name(event.input.type)); |                     input_get_type_name(event.input.type)); | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "desktop_i.h" | #include "desktop_i.h" | ||||||
|  | #include <furi-hal-lock.h> | ||||||
| 
 | 
 | ||||||
| static void desktop_lock_icon_callback(Canvas* canvas, void* context) { | static void desktop_lock_icon_callback(Canvas* canvas, void* context) { | ||||||
|     furi_assert(canvas); |     furi_assert(canvas); | ||||||
| @ -41,6 +42,7 @@ Desktop* desktop_alloc() { | |||||||
|     desktop->debug_view = desktop_debug_alloc(); |     desktop->debug_view = desktop_debug_alloc(); | ||||||
|     desktop->first_start_view = desktop_first_start_alloc(); |     desktop->first_start_view = desktop_first_start_alloc(); | ||||||
|     desktop->hw_mismatch_popup = popup_alloc(); |     desktop->hw_mismatch_popup = popup_alloc(); | ||||||
|  |     desktop->code_input = code_input_alloc(); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view)); |         desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view)); | ||||||
| @ -62,7 +64,8 @@ Desktop* desktop_alloc() { | |||||||
|         desktop->view_dispatcher, |         desktop->view_dispatcher, | ||||||
|         DesktopViewHwMismatch, |         DesktopViewHwMismatch, | ||||||
|         popup_get_view(desktop->hw_mismatch_popup)); |         popup_get_view(desktop->hw_mismatch_popup)); | ||||||
| 
 |     view_dispatcher_add_view( | ||||||
|  |         desktop->view_dispatcher, DesktopViewPinSetup, code_input_get_view(desktop->code_input)); | ||||||
|     // Lock icon
 |     // Lock icon
 | ||||||
|     desktop->lock_viewport = view_port_alloc(); |     desktop->lock_viewport = view_port_alloc(); | ||||||
|     view_port_set_width(desktop->lock_viewport, icon_get_width(&I_Lock_8x8)); |     view_port_set_width(desktop->lock_viewport, icon_get_width(&I_Lock_8x8)); | ||||||
| @ -82,6 +85,7 @@ void desktop_free(Desktop* desktop) { | |||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewDebug); |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewDebug); | ||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewFirstStart); |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewFirstStart); | ||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewHwMismatch); |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewHwMismatch); | ||||||
|  |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewPinSetup); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_free(desktop->view_dispatcher); |     view_dispatcher_free(desktop->view_dispatcher); | ||||||
|     scene_manager_free(desktop->scene_manager); |     scene_manager_free(desktop->scene_manager); | ||||||
| @ -92,6 +96,7 @@ void desktop_free(Desktop* desktop) { | |||||||
|     desktop_debug_free(desktop->debug_view); |     desktop_debug_free(desktop->debug_view); | ||||||
|     desktop_first_start_free(desktop->first_start_view); |     desktop_first_start_free(desktop->first_start_view); | ||||||
|     popup_free(desktop->hw_mismatch_popup); |     popup_free(desktop->hw_mismatch_popup); | ||||||
|  |     code_input_free(desktop->code_input); | ||||||
| 
 | 
 | ||||||
|     furi_record_close("gui"); |     furi_record_close("gui"); | ||||||
|     desktop->gui = NULL; |     desktop->gui = NULL; | ||||||
| @ -113,9 +118,22 @@ static bool desktop_is_first_start() { | |||||||
| 
 | 
 | ||||||
| int32_t desktop_srv(void* p) { | int32_t desktop_srv(void* p) { | ||||||
|     Desktop* desktop = desktop_alloc(); |     Desktop* desktop = desktop_alloc(); | ||||||
|  |     bool loaded = LOAD_DESKTOP_SETTINGS(&desktop->settings); | ||||||
|  |     if(!loaded) { | ||||||
|  |         furi_hal_lock_set(false); | ||||||
|  |         memset(&desktop->settings, 0, sizeof(desktop->settings)); | ||||||
|  |         SAVE_DESKTOP_SETTINGS(&desktop->settings); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); |     scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||||
| 
 | 
 | ||||||
|  |     if(furi_hal_lock_get()) { | ||||||
|  |         furi_hal_usb_disable(); | ||||||
|  |         scene_manager_set_scene_state( | ||||||
|  |             desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin); | ||||||
|  |         scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if(desktop_is_first_start()) { |     if(desktop_is_first_start()) { | ||||||
|         scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart); |         scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ | |||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
| #include <gui/modules/popup.h> | #include <gui/modules/popup.h> | ||||||
|  | #include <gui/modules/code_input.h> | ||||||
| #include <gui/scene_manager.h> | #include <gui/scene_manager.h> | ||||||
| #include <assets_icons.h> | #include <assets_icons.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| @ -29,6 +30,7 @@ typedef enum { | |||||||
|     DesktopViewDebug, |     DesktopViewDebug, | ||||||
|     DesktopViewFirstStart, |     DesktopViewFirstStart, | ||||||
|     DesktopViewHwMismatch, |     DesktopViewHwMismatch, | ||||||
|  |     DesktopViewPinSetup, | ||||||
|     DesktopViewTotal, |     DesktopViewTotal, | ||||||
| } DesktopViewEnum; | } DesktopViewEnum; | ||||||
| 
 | 
 | ||||||
| @ -46,7 +48,10 @@ struct Desktop { | |||||||
|     DesktopLockMenuView* lock_menu; |     DesktopLockMenuView* lock_menu; | ||||||
|     DesktopLockedView* locked_view; |     DesktopLockedView* locked_view; | ||||||
|     DesktopDebugView* debug_view; |     DesktopDebugView* debug_view; | ||||||
|  |     CodeInput* code_input; | ||||||
|  | 
 | ||||||
|     DesktopSettings settings; |     DesktopSettings settings; | ||||||
|  |     PinCode pincode_buffer; | ||||||
| 
 | 
 | ||||||
|     ViewPort* lock_viewport; |     ViewPort* lock_viewport; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,49 +0,0 @@ | |||||||
| #include <furi.h> |  | ||||||
| #include <file-worker.h> |  | ||||||
| #include "desktop_settings.h" |  | ||||||
| 
 |  | ||||||
| #define DESKTOP_SETTINGS_TAG "Desktop settings" |  | ||||||
| #define DESKTOP_SETTINGS_PATH "/int/desktop.settings" |  | ||||||
| 
 |  | ||||||
| bool desktop_settings_load(DesktopSettings* desktop_settings) { |  | ||||||
|     furi_assert(desktop_settings); |  | ||||||
|     bool file_loaded = false; |  | ||||||
|     DesktopSettings settings = {}; |  | ||||||
| 
 |  | ||||||
|     FURI_LOG_I(DESKTOP_SETTINGS_TAG, "Loading settings from \"%s\"", DESKTOP_SETTINGS_PATH); |  | ||||||
|     FileWorker* file_worker = file_worker_alloc(true); |  | ||||||
|     if(file_worker_open(file_worker, DESKTOP_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { |  | ||||||
|         if(file_worker_read(file_worker, &settings, sizeof(settings))) { |  | ||||||
|             file_loaded = true; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     file_worker_free(file_worker); |  | ||||||
| 
 |  | ||||||
|     if(file_loaded) { |  | ||||||
|         if(settings.version != DESKTOP_SETTINGS_VER) { |  | ||||||
|             FURI_LOG_E(DESKTOP_SETTINGS_TAG, "Settings version mismatch"); |  | ||||||
|         } else { |  | ||||||
|             osKernelLock(); |  | ||||||
|             *desktop_settings = settings; |  | ||||||
|             osKernelUnlock(); |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         FURI_LOG_E(DESKTOP_SETTINGS_TAG, "Settings load failed"); |  | ||||||
|     } |  | ||||||
|     return file_loaded; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool desktop_settings_save(DesktopSettings* desktop_settings) { |  | ||||||
|     furi_assert(desktop_settings); |  | ||||||
|     bool result = false; |  | ||||||
| 
 |  | ||||||
|     FileWorker* file_worker = file_worker_alloc(true); |  | ||||||
|     if(file_worker_open(file_worker, DESKTOP_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) { |  | ||||||
|         if(file_worker_write(file_worker, desktop_settings, sizeof(DesktopSettings))) { |  | ||||||
|             FURI_LOG_I(DESKTOP_SETTINGS_TAG, "Settings saved to \"%s\"", DESKTOP_SETTINGS_PATH); |  | ||||||
|             result = true; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     file_worker_free(file_worker); |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| @ -2,14 +2,35 @@ | |||||||
| 
 | 
 | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
|  | #include <toolbox/saved_struct.h> | ||||||
| 
 | 
 | ||||||
| #define DESKTOP_SETTINGS_VER (0) | #define DESKTOP_SETTINGS_VER (1) | ||||||
|  | #define DESKTOP_SETTINGS_PATH "/int/desktop.settings" | ||||||
|  | #define DESKTOP_SETTINGS_MAGIC (0x17) | ||||||
|  | #define PIN_MAX_LENGTH 12 | ||||||
|  | 
 | ||||||
|  | #define SAVE_DESKTOP_SETTINGS(x) \ | ||||||
|  |     saved_struct_save(           \ | ||||||
|  |         DESKTOP_SETTINGS_PATH,   \ | ||||||
|  |         (x),                     \ | ||||||
|  |         sizeof(DesktopSettings), \ | ||||||
|  |         DESKTOP_SETTINGS_MAGIC,  \ | ||||||
|  |         DESKTOP_SETTINGS_VER) | ||||||
|  | 
 | ||||||
|  | #define LOAD_DESKTOP_SETTINGS(x) \ | ||||||
|  |     saved_struct_load(           \ | ||||||
|  |         DESKTOP_SETTINGS_PATH,   \ | ||||||
|  |         (x),                     \ | ||||||
|  |         sizeof(DesktopSettings), \ | ||||||
|  |         DESKTOP_SETTINGS_MAGIC,  \ | ||||||
|  |         DESKTOP_SETTINGS_VER) | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t length; | ||||||
|  |     uint8_t data[PIN_MAX_LENGTH]; | ||||||
|  | } PinCode; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t version; |  | ||||||
|     uint16_t favorite; |     uint16_t favorite; | ||||||
|  |     PinCode pincode; | ||||||
| } DesktopSettings; | } DesktopSettings; | ||||||
| 
 |  | ||||||
| bool desktop_settings_load(DesktopSettings* desktop_settings); |  | ||||||
| 
 |  | ||||||
| bool desktop_settings_save(DesktopSettings* desktop_settings); |  | ||||||
|  | |||||||
| @ -15,9 +15,6 @@ static bool desktop_settings_back_event_callback(void* context) { | |||||||
| DesktopSettingsApp* desktop_settings_app_alloc() { | DesktopSettingsApp* desktop_settings_app_alloc() { | ||||||
|     DesktopSettingsApp* app = furi_alloc(sizeof(DesktopSettingsApp)); |     DesktopSettingsApp* app = furi_alloc(sizeof(DesktopSettingsApp)); | ||||||
| 
 | 
 | ||||||
|     app->settings.version = DESKTOP_SETTINGS_VER; |  | ||||||
|     desktop_settings_load(&app->settings); |  | ||||||
| 
 |  | ||||||
|     app->gui = furi_record_open("gui"); |     app->gui = furi_record_open("gui"); | ||||||
|     app->view_dispatcher = view_dispatcher_alloc(); |     app->view_dispatcher = view_dispatcher_alloc(); | ||||||
|     app->scene_manager = scene_manager_alloc(&desktop_settings_scene_handlers, app); |     app->scene_manager = scene_manager_alloc(&desktop_settings_scene_handlers, app); | ||||||
| @ -33,10 +30,13 @@ DesktopSettingsApp* desktop_settings_app_alloc() { | |||||||
| 
 | 
 | ||||||
|     app->submenu = submenu_alloc(); |     app->submenu = submenu_alloc(); | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         app->view_dispatcher, DesktopSettingsAppViewMain, submenu_get_view(app->submenu)); |         app->view_dispatcher, DesktopSettingsAppViewMenu, submenu_get_view(app->submenu)); | ||||||
| 
 | 
 | ||||||
|  |     app->code_input = code_input_alloc(); | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         app->view_dispatcher, DesktopSettingsAppViewFavorite, submenu_get_view(app->submenu)); |         app->view_dispatcher, | ||||||
|  |         DesktopSettingsAppViewPincodeInput, | ||||||
|  |         code_input_get_view(app->code_input)); | ||||||
| 
 | 
 | ||||||
|     scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart); |     scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart); | ||||||
|     return app; |     return app; | ||||||
| @ -45,9 +45,10 @@ DesktopSettingsApp* desktop_settings_app_alloc() { | |||||||
| void desktop_settings_app_free(DesktopSettingsApp* app) { | void desktop_settings_app_free(DesktopSettingsApp* app) { | ||||||
|     furi_assert(app); |     furi_assert(app); | ||||||
|     // Variable item list
 |     // Variable item list
 | ||||||
|     view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMain); |     view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMenu); | ||||||
|     view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewFavorite); |  | ||||||
|     submenu_free(app->submenu); |     submenu_free(app->submenu); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewPincodeInput); | ||||||
|  |     code_input_free(app->code_input); | ||||||
|     // View dispatcher
 |     // View dispatcher
 | ||||||
|     view_dispatcher_free(app->view_dispatcher); |     view_dispatcher_free(app->view_dispatcher); | ||||||
|     scene_manager_free(app->scene_manager); |     scene_manager_free(app->scene_manager); | ||||||
| @ -58,8 +59,8 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { | |||||||
| 
 | 
 | ||||||
| extern int32_t desktop_settings_app(void* p) { | extern int32_t desktop_settings_app(void* p) { | ||||||
|     DesktopSettingsApp* app = desktop_settings_app_alloc(); |     DesktopSettingsApp* app = desktop_settings_app_alloc(); | ||||||
|  |     LOAD_DESKTOP_SETTINGS(&app->settings); | ||||||
|     view_dispatcher_run(app->view_dispatcher); |     view_dispatcher_run(app->view_dispatcher); | ||||||
|     desktop_settings_save(&app->settings); |  | ||||||
|     desktop_settings_app_free(app); |     desktop_settings_app_free(app); | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,20 +6,32 @@ | |||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
| #include <gui/scene_manager.h> | #include <gui/scene_manager.h> | ||||||
| #include <gui/modules/submenu.h> | #include <gui/modules/submenu.h> | ||||||
|  | #include <gui/modules/code_input.h> | ||||||
| 
 | 
 | ||||||
| #include "desktop_settings.h" | #include "desktop_settings.h" | ||||||
|  | 
 | ||||||
| #include "scenes/desktop_settings_scene.h" | #include "scenes/desktop_settings_scene.h" | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     DesktopSettingsAppViewMain, |     CodeEventsSetPin, | ||||||
|     DesktopSettingsAppViewFavorite, |     CodeEventsChangePin, | ||||||
|  |     CodeEventsDisablePin, | ||||||
|  | } CodeEventsEnum; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopSettingsAppViewMenu, | ||||||
|  |     DesktopSettingsAppViewPincodeInput, | ||||||
| } DesktopSettingsAppView; | } DesktopSettingsAppView; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     DesktopSettings settings; |     DesktopSettings settings; | ||||||
|  | 
 | ||||||
|     Gui* gui; |     Gui* gui; | ||||||
|     SceneManager* scene_manager; |     SceneManager* scene_manager; | ||||||
|     ViewDispatcher* view_dispatcher; |     ViewDispatcher* view_dispatcher; | ||||||
|     Submenu* submenu; |     Submenu* submenu; | ||||||
|  |     CodeInput* code_input; | ||||||
|  | 
 | ||||||
|  |     uint8_t menu_idx; | ||||||
| 
 | 
 | ||||||
| } DesktopSettingsApp; | } DesktopSettingsApp; | ||||||
|  | |||||||
| @ -1,2 +1,4 @@ | |||||||
| ADD_SCENE(desktop_settings, start, Start) | ADD_SCENE(desktop_settings, start, Start) | ||||||
| ADD_SCENE(desktop_settings, favorite, Favorite) | ADD_SCENE(desktop_settings, favorite, Favorite) | ||||||
|  | ADD_SCENE(desktop_settings, pincode_menu, PinCodeMenu) | ||||||
|  | ADD_SCENE(desktop_settings, pincode_input, PinCodeInput) | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| #include "../desktop_settings_app.h" | #include "../desktop_settings_app.h" | ||||||
| #include "applications.h" | #include "applications.h" | ||||||
|  | #include "desktop/desktop_settings/desktop_settings.h" | ||||||
| 
 | 
 | ||||||
| static void desktop_settings_scene_favorite_submenu_callback(void* context, uint32_t index) { | static void desktop_settings_scene_favorite_submenu_callback(void* context, uint32_t index) { | ||||||
|     DesktopSettingsApp* app = context; |     DesktopSettingsApp* app = context; | ||||||
| @ -22,7 +23,7 @@ void desktop_settings_scene_favorite_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     submenu_set_header(app->submenu, "Quick access app:"); |     submenu_set_header(app->submenu, "Quick access app:"); | ||||||
|     submenu_set_selected_item(app->submenu, app->settings.favorite); |     submenu_set_selected_item(app->submenu, app->settings.favorite); | ||||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewFavorite); |     view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent event) { | bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent event) { | ||||||
| @ -43,5 +44,6 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e | |||||||
| 
 | 
 | ||||||
| void desktop_settings_scene_favorite_on_exit(void* context) { | void desktop_settings_scene_favorite_on_exit(void* context) { | ||||||
|     DesktopSettingsApp* app = context; |     DesktopSettingsApp* app = context; | ||||||
|  |     SAVE_DESKTOP_SETTINGS(&app->settings); | ||||||
|     submenu_clean(app->submenu); |     submenu_clean(app->submenu); | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,64 @@ | |||||||
|  | #include "../desktop_settings_app.h" | ||||||
|  | #include "desktop/desktop_settings/desktop_settings.h" | ||||||
|  | 
 | ||||||
|  | #define SCENE_EXIT_EVENT (0U) | ||||||
|  | 
 | ||||||
|  | void desktop_settings_scene_ok_callback(void* context) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     uint32_t state = | ||||||
|  |         scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinCodeInput); | ||||||
|  | 
 | ||||||
|  |     if(state == CodeEventsDisablePin) { | ||||||
|  |         memset(app->settings.pincode.data, 0, app->settings.pincode.length * sizeof(uint8_t)); | ||||||
|  |         app->settings.pincode.length = 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_settings_scene_pincode_input_on_enter(void* context) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     CodeInput* code_input = app->code_input; | ||||||
|  | 
 | ||||||
|  |     uint32_t state = | ||||||
|  |         scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinCodeInput); | ||||||
|  |     bool update = state != CodeEventsDisablePin; | ||||||
|  | 
 | ||||||
|  |     code_input_set_header_text(code_input, "PIN Code Setup"); | ||||||
|  |     code_input_set_result_callback( | ||||||
|  |         code_input, | ||||||
|  |         desktop_settings_scene_ok_callback, | ||||||
|  |         NULL, | ||||||
|  |         app, | ||||||
|  |         app->settings.pincode.data, | ||||||
|  |         &app->settings.pincode.length, | ||||||
|  |         update); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewPincodeInput); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_settings_scene_pincode_input_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case SCENE_EXIT_EVENT: | ||||||
|  |             scene_manager_previous_scene(app->scene_manager); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         default: | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_settings_scene_pincode_input_on_exit(void* context) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     SAVE_DESKTOP_SETTINGS(&app->settings); | ||||||
|  |     code_input_set_result_callback(app->code_input, NULL, NULL, NULL, NULL, NULL, 0); | ||||||
|  |     code_input_set_header_text(app->code_input, ""); | ||||||
|  | } | ||||||
| @ -0,0 +1,78 @@ | |||||||
|  | #include "../desktop_settings_app.h" | ||||||
|  | #include "applications.h" | ||||||
|  | 
 | ||||||
|  | static void desktop_settings_scene_pincode_menu_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_settings_scene_pincode_menu_on_enter(void* context) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     Submenu* submenu = app->submenu; | ||||||
|  |     submenu_clean(submenu); | ||||||
|  | 
 | ||||||
|  |     if(!app->settings.pincode.length) { | ||||||
|  |         submenu_add_item( | ||||||
|  |             submenu, | ||||||
|  |             "Set Pin", | ||||||
|  |             CodeEventsSetPin, | ||||||
|  |             desktop_settings_scene_pincode_menu_submenu_callback, | ||||||
|  |             app); | ||||||
|  | 
 | ||||||
|  |     } else { | ||||||
|  |         submenu_add_item( | ||||||
|  |             submenu, | ||||||
|  |             "Change Pin", | ||||||
|  |             CodeEventsChangePin, | ||||||
|  |             desktop_settings_scene_pincode_menu_submenu_callback, | ||||||
|  |             app); | ||||||
|  | 
 | ||||||
|  |         submenu_add_item( | ||||||
|  |             submenu, | ||||||
|  |             "Disable", | ||||||
|  |             CodeEventsDisablePin, | ||||||
|  |             desktop_settings_scene_pincode_menu_submenu_callback, | ||||||
|  |             app); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     submenu_set_header(app->submenu, "Pin code settings:"); | ||||||
|  |     submenu_set_selected_item(app->submenu, app->menu_idx); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_settings_scene_pincode_menu_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case CodeEventsSetPin: | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event); | ||||||
|  |             scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case CodeEventsChangePin: | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event); | ||||||
|  |             scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case CodeEventsDisablePin: | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event); | ||||||
|  |             scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         default: | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_settings_scene_pincode_menu_on_exit(void* context) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     submenu_clean(app->submenu); | ||||||
|  | } | ||||||
| @ -29,7 +29,7 @@ void desktop_settings_scene_start_on_enter(void* context) { | |||||||
|         desktop_settings_scene_start_submenu_callback, |         desktop_settings_scene_start_submenu_callback, | ||||||
|         app); |         app); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMain); |     view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent event) { | bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||||
| @ -39,7 +39,11 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even | |||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         switch(event.event) { |         switch(event.event) { | ||||||
|         case DesktopSettingsStartSubmenuIndexFavorite: |         case DesktopSettingsStartSubmenuIndexFavorite: | ||||||
|             scene_manager_next_scene(app->scene_manager, DesktopSettingsAppViewFavorite); |             scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case DesktopSettingsStartSubmenuIndexPinSetup: | ||||||
|  |             scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeMenu); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
| #include "desktop_animation.h" | #include "desktop_animation.h" | ||||||
| 
 | 
 | ||||||
|  | #define TAG "DesktopAnimation" | ||||||
|  | 
 | ||||||
| static const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64}; | static const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64}; | ||||||
| 
 | 
 | ||||||
| const Icon* desktop_get_icon() { | const Icon* desktop_get_icon() { | ||||||
| @ -12,10 +14,10 @@ const Icon* desktop_get_icon() { | |||||||
|     DolphinStats stats = dolphin_stats(dolphin); |     DolphinStats stats = dolphin_stats(dolphin); | ||||||
|     float timediff = fabs(difftime(stats.timestamp, dolphin_state_timestamp())); |     float timediff = fabs(difftime(stats.timestamp, dolphin_state_timestamp())); | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I("desktop-animation", "background change"); |     FURI_LOG_I(TAG, "background change"); | ||||||
|     FURI_LOG_I("desktop-animation", "icounter: %d", stats.icounter); |     FURI_LOG_I(TAG, "icounter: %d", stats.icounter); | ||||||
|     FURI_LOG_I("desktop-animation", "butthurt: %d", stats.butthurt); |     FURI_LOG_I(TAG, "butthurt: %d", stats.butthurt); | ||||||
|     FURI_LOG_I("desktop-animation", "time since deeed: %.0f", timediff); |     FURI_LOG_I(TAG, "time since deeed: %.0f", timediff); | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|     if((random() % 100) > 50) { // temp rnd selection
 |     if((random() % 100) > 50) { // temp rnd selection
 | ||||||
|  | |||||||
| @ -4,3 +4,4 @@ ADD_SCENE(desktop, locked, Locked) | |||||||
| ADD_SCENE(desktop, debug, Debug) | ADD_SCENE(desktop, debug, Debug) | ||||||
| ADD_SCENE(desktop, first_start, FirstStart) | ADD_SCENE(desktop, first_start, FirstStart) | ||||||
| ADD_SCENE(desktop, hw_mismatch, HwMismatch) | ADD_SCENE(desktop, hw_mismatch, HwMismatch) | ||||||
|  | ADD_SCENE(desktop, pinsetup, PinSetup) | ||||||
|  | |||||||
| @ -1,5 +1,8 @@ | |||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| #include "../views/desktop_lock_menu.h" | #include "../views/desktop_lock_menu.h" | ||||||
|  | #include <toolbox/saved_struct.h> | ||||||
|  | #include <stdbool.h> | ||||||
|  | #include <furi-hal-lock.h> | ||||||
| 
 | 
 | ||||||
| void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context) { | void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
| @ -9,7 +12,10 @@ void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context) | |||||||
| void desktop_scene_lock_menu_on_enter(void* context) { | void desktop_scene_lock_menu_on_enter(void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
| 
 | 
 | ||||||
|  |     LOAD_DESKTOP_SETTINGS(&desktop->settings); | ||||||
|  | 
 | ||||||
|     desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop); |     desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop); | ||||||
|  |     desktop_lock_menu_pin_set(desktop->lock_menu, desktop->settings.pincode.length > 0); | ||||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLockMenu); |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLockMenu); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -20,15 +26,29 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { | |||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         switch(event.event) { |         switch(event.event) { | ||||||
|         case DesktopLockMenuEventLock: |         case DesktopLockMenuEventLock: | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 desktop->scene_manager, DesktopSceneLocked, DesktopLockedNoPin); | ||||||
|             scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); |             scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|  |         case DesktopLockMenuEventPinLock: | ||||||
|  |             if(desktop->settings.pincode.length > 0) { | ||||||
|  |                 furi_hal_lock_set(true); | ||||||
|  |                 furi_hal_usb_disable(); | ||||||
|  |                 scene_manager_set_scene_state( | ||||||
|  |                     desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin); | ||||||
|  |                 scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); | ||||||
|  |             } else { | ||||||
|  |                 scene_manager_next_scene(desktop->scene_manager, DesktopScenePinSetup); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|         case DesktopLockMenuEventExit: |  | ||||||
|             scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); |  | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
| 
 |         case DesktopLockMenuEventExit: | ||||||
|  |             scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                 desktop->scene_manager, DesktopSceneMain); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|         default: |         default: | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| #include "../views/desktop_locked.h" | #include "../views/desktop_locked.h" | ||||||
|  | #include <furi-hal-lock.h> | ||||||
| 
 | 
 | ||||||
| void desktop_scene_locked_callback(DesktopLockedEvent event, void* context) { | void desktop_scene_locked_callback(DesktopLockedEvent event, void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
| @ -15,12 +16,39 @@ void desktop_scene_locked_on_enter(void* context) { | |||||||
|     desktop_locked_update_hint_timeout(locked_view); |     desktop_locked_update_hint_timeout(locked_view); | ||||||
|     desktop_locked_set_dolphin_animation(locked_view); |     desktop_locked_set_dolphin_animation(locked_view); | ||||||
| 
 | 
 | ||||||
|  |     uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked); | ||||||
|  | 
 | ||||||
|  |     desktop_locked_with_pin(desktop->locked_view, state == DesktopLockedWithPin); | ||||||
|  | 
 | ||||||
|     view_port_enabled_set(desktop->lock_viewport, true); |     view_port_enabled_set(desktop->lock_viewport, true); | ||||||
|     osTimerStart(locked_view->timer, 63); |     osTimerStart(locked_view->timer, 63); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked); |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopMainEvent event) { | ||||||
|  |     bool match = false; | ||||||
|  | 
 | ||||||
|  |     size_t length = desktop->pincode_buffer.length; | ||||||
|  |     length = code_input_push(desktop->pincode_buffer.data, length, event); | ||||||
|  |     desktop->pincode_buffer.length = length; | ||||||
|  | 
 | ||||||
|  |     match = code_input_compare( | ||||||
|  |         desktop->pincode_buffer.data, | ||||||
|  |         length, | ||||||
|  |         desktop->settings.pincode.data, | ||||||
|  |         desktop->settings.pincode.length); | ||||||
|  | 
 | ||||||
|  |     if(match) { | ||||||
|  |         desktop->pincode_buffer.length = 0; | ||||||
|  |         furi_hal_usb_enable(); | ||||||
|  |         furi_hal_lock_set(false); | ||||||
|  |         desktop_main_unlocked(desktop->main_view); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return match; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { | bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
| 
 | 
 | ||||||
| @ -36,7 +64,17 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { | |||||||
|         case DesktopLockedEventUpdate: |         case DesktopLockedEventUpdate: | ||||||
|             desktop_locked_manage_redraw(desktop->locked_view); |             desktop_locked_manage_redraw(desktop->locked_view); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case DesktopLockedEventInputReset: | ||||||
|  |             desktop->pincode_buffer.length = 0; | ||||||
|  |             break; | ||||||
|         default: |         default: | ||||||
|  |             if(desktop_scene_locked_check_pin(desktop, event.event)) { | ||||||
|  |                 scene_manager_set_scene_state( | ||||||
|  |                     desktop->scene_manager, DesktopSceneMain, DesktopMainEventUnlocked); | ||||||
|  |                 scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||||
|  |                 consumed = true; | ||||||
|  |             } | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -70,7 +70,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | |||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case DesktopMainEventOpenFavorite: |         case DesktopMainEventOpenFavorite: | ||||||
|             desktop_settings_load(&desktop->settings); |             LOAD_DESKTOP_SETTINGS(&desktop->settings); | ||||||
|             desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]); |             desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|  | |||||||
							
								
								
									
										50
									
								
								applications/desktop/scenes/desktop_scene_pinsetup.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								applications/desktop/scenes/desktop_scene_pinsetup.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | |||||||
|  | #include "../desktop_i.h" | ||||||
|  | 
 | ||||||
|  | #define SCENE_EXIT_EVENT (0U) | ||||||
|  | 
 | ||||||
|  | void desktop_scene_ok_callback(void* context) { | ||||||
|  |     Desktop* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_pinsetup_on_enter(void* context) { | ||||||
|  |     Desktop* app = context; | ||||||
|  |     CodeInput* code_input = app->code_input; | ||||||
|  | 
 | ||||||
|  |     code_input_set_result_callback( | ||||||
|  |         code_input, | ||||||
|  |         desktop_scene_ok_callback, | ||||||
|  |         NULL, | ||||||
|  |         app, | ||||||
|  |         app->settings.pincode.data, | ||||||
|  |         &app->settings.pincode.length, | ||||||
|  |         true); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, DesktopViewPinSetup); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_scene_pinsetup_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Desktop* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case SCENE_EXIT_EVENT: | ||||||
|  |             scene_manager_previous_scene(app->scene_manager); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         default: | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_pinsetup_on_exit(void* context) { | ||||||
|  |     Desktop* app = context; | ||||||
|  |     SAVE_DESKTOP_SETTINGS(&app->settings); | ||||||
|  |     code_input_set_result_callback(app->code_input, NULL, NULL, NULL, NULL, NULL, 0); | ||||||
|  |     code_input_set_header_text(app->code_input, ""); | ||||||
|  | } | ||||||
| @ -42,7 +42,7 @@ void desktop_debug_render(Canvas* canvas, void* model) { | |||||||
|             my_name ? my_name : "Unknown"); |             my_name ? my_name : "Unknown"); | ||||||
|         canvas_draw_str(canvas, 5, 23, buffer); |         canvas_draw_str(canvas, 5, 23, buffer); | ||||||
| 
 | 
 | ||||||
|         ver = m->screen == DesktopViewStatsBoot ? furi_hal_version_get_boot_version() : |         ver = m->screen == DesktopViewStatsBoot ? furi_hal_version_get_bootloader_version() : | ||||||
|                                                   furi_hal_version_get_firmware_version(); |                                                   furi_hal_version_get_firmware_version(); | ||||||
| 
 | 
 | ||||||
|         if(!ver) { |         if(!ver) { | ||||||
|  | |||||||
| @ -12,6 +12,14 @@ void desktop_lock_menu_set_callback( | |||||||
|     lock_menu->context = context; |     lock_menu->context = context; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set) { | ||||||
|  |     with_view_model( | ||||||
|  |         lock_menu->view, (DesktopLockMenuViewModel * model) { | ||||||
|  |             model->pin_set = pin_is_set; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu) { | void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu) { | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         lock_menu->view, (DesktopLockMenuViewModel * model) { |         lock_menu->view, (DesktopLockMenuViewModel * model) { | ||||||
| @ -26,6 +34,10 @@ static void lock_menu_callback(void* context, uint8_t index) { | |||||||
|     switch(index) { |     switch(index) { | ||||||
|     case 0: // lock
 |     case 0: // lock
 | ||||||
|         lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); |         lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); | ||||||
|  |         break; | ||||||
|  |     case 1: // lock
 | ||||||
|  |         lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context); | ||||||
|  |         break; | ||||||
|     default: // wip message
 |     default: // wip message
 | ||||||
|         with_view_model( |         with_view_model( | ||||||
|             lock_menu->view, (DesktopLockMenuViewModel * model) { |             lock_menu->view, (DesktopLockMenuViewModel * model) { | ||||||
| @ -37,7 +49,7 @@ static void lock_menu_callback(void* context, uint8_t index) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_lock_menu_render(Canvas* canvas, void* model) { | void desktop_lock_menu_render(Canvas* canvas, void* model) { | ||||||
|     const char* Lockmenu_Items[3] = {"Lock", "Set PIN", "DUMB mode"}; |     const char* Lockmenu_Items[3] = {"Lock", "Lock with PIN", "DUMB mode"}; | ||||||
| 
 | 
 | ||||||
|     DesktopLockMenuViewModel* m = model; |     DesktopLockMenuViewModel* m = model; | ||||||
|     canvas_clear(canvas); |     canvas_clear(canvas); | ||||||
| @ -47,13 +59,13 @@ void desktop_lock_menu_render(Canvas* canvas, void* model) { | |||||||
|     canvas_set_font(canvas, FontSecondary); |     canvas_set_font(canvas, FontSecondary); | ||||||
| 
 | 
 | ||||||
|     for(uint8_t i = 0; i < 3; ++i) { |     for(uint8_t i = 0; i < 3; ++i) { | ||||||
|         canvas_draw_str_aligned( |         const char* str = Lockmenu_Items[i]; | ||||||
|             canvas, | 
 | ||||||
|             64, |         if(i == 1 && !m->pin_set) str = "Set PIN"; | ||||||
|             13 + (i * 17), |         if(m->hint_timeout && m->idx == 2 && m->idx == i) str = "Not implemented"; | ||||||
|             AlignCenter, | 
 | ||||||
|             AlignCenter, |         canvas_draw_str_aligned(canvas, 64, 13 + (i * 17), AlignCenter, AlignCenter, str); | ||||||
|             (m->hint_timeout && m->idx == i && m->idx) ? "Not implemented" : Lockmenu_Items[i]); | 
 | ||||||
|         if(m->idx == i) elements_frame(canvas, 15, 5 + (i * 17), 98, 15); |         if(m->idx == i) elements_frame(canvas, 15, 5 + (i * 17), 98, 15); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ | |||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     DesktopLockMenuEventLock, |     DesktopLockMenuEventLock, | ||||||
|     DesktopLockMenuEventUnlock, |     DesktopLockMenuEventPinLock, | ||||||
|     DesktopLockMenuEventExit, |     DesktopLockMenuEventExit, | ||||||
| } DesktopLockMenuEvent; | } DesktopLockMenuEvent; | ||||||
| 
 | 
 | ||||||
| @ -27,6 +27,7 @@ struct DesktopLockMenuView { | |||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t idx; |     uint8_t idx; | ||||||
|     uint8_t hint_timeout; |     uint8_t hint_timeout; | ||||||
|  |     bool pin_set; | ||||||
| } DesktopLockMenuViewModel; | } DesktopLockMenuViewModel; | ||||||
| 
 | 
 | ||||||
| void desktop_lock_menu_set_callback( | void desktop_lock_menu_set_callback( | ||||||
| @ -35,6 +36,7 @@ void desktop_lock_menu_set_callback( | |||||||
|     void* context); |     void* context); | ||||||
| 
 | 
 | ||||||
| View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu); | View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu); | ||||||
|  | void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set); | ||||||
| void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu); | void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu); | ||||||
| DesktopLockMenuView* desktop_lock_menu_alloc(); | DesktopLockMenuView* desktop_lock_menu_alloc(); | ||||||
| void desktop_lock_menu_free(DesktopLockMenuView* lock_menu); | void desktop_lock_menu_free(DesktopLockMenuView* lock_menu); | ||||||
|  | |||||||
| @ -80,6 +80,14 @@ void desktop_locked_reset_counter(DesktopLockedView* locked_view) { | |||||||
|         }); |         }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void desktop_locked_with_pin(DesktopLockedView* locked_view, bool locked) { | ||||||
|  |     with_view_model( | ||||||
|  |         locked_view->view, (DesktopLockedViewModel * model) { | ||||||
|  |             model->pin_lock = locked; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void desktop_locked_render(Canvas* canvas, void* model) { | void desktop_locked_render(Canvas* canvas, void* model) { | ||||||
|     DesktopLockedViewModel* m = model; |     DesktopLockedViewModel* m = model; | ||||||
|     uint32_t now = osKernelGetTickCount(); |     uint32_t now = osKernelGetTickCount(); | ||||||
| @ -100,7 +108,7 @@ void desktop_locked_render(Canvas* canvas, void* model) { | |||||||
|             canvas_set_font(canvas, FontPrimary); |             canvas_set_font(canvas, FontPrimary); | ||||||
|             elements_multiline_text_framed(canvas, 42, 30, "Locked"); |             elements_multiline_text_framed(canvas, 42, 30, "Locked"); | ||||||
| 
 | 
 | ||||||
|         } else { |         } else if(!m->pin_lock) { | ||||||
|             canvas_set_font(canvas, FontSecondary); |             canvas_set_font(canvas, FontSecondary); | ||||||
|             canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49); |             canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49); | ||||||
|             elements_multiline_text(canvas, 65, 20, "To unlock\npress:"); |             elements_multiline_text(canvas, 65, 20, "To unlock\npress:"); | ||||||
| @ -116,18 +124,34 @@ View* desktop_locked_get_view(DesktopLockedView* locked_view) { | |||||||
| bool desktop_locked_input(InputEvent* event, void* context) { | bool desktop_locked_input(InputEvent* event, void* context) { | ||||||
|     furi_assert(event); |     furi_assert(event); | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 |  | ||||||
|     DesktopLockedView* locked_view = context; |     DesktopLockedView* locked_view = context; | ||||||
|  | 
 | ||||||
|  |     uint32_t press_time = 0; | ||||||
|  |     bool locked_with_pin = false; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         locked_view->view, (DesktopLockedViewModel * model) { | ||||||
|  |             locked_with_pin = model->pin_lock; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|     if(event->type == InputTypeShort) { |     if(event->type == InputTypeShort) { | ||||||
|  |         if(locked_with_pin) { | ||||||
|  |             press_time = osKernelGetTickCount(); | ||||||
|  | 
 | ||||||
|  |             if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT * 3) { | ||||||
|  |                 locked_view->lock_lastpress = press_time; | ||||||
|  |                 locked_view->callback(DesktopLockedEventInputReset, locked_view->context); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             locked_view->callback(event->key, locked_view->context); | ||||||
|  |         } else { | ||||||
|             desktop_locked_update_hint_timeout(locked_view); |             desktop_locked_update_hint_timeout(locked_view); | ||||||
| 
 | 
 | ||||||
|             if(event->key == InputKeyBack) { |             if(event->key == InputKeyBack) { | ||||||
|             uint32_t press_time = osKernelGetTickCount(); |                 press_time = osKernelGetTickCount(); | ||||||
|                 // check if pressed sequentially
 |                 // check if pressed sequentially
 | ||||||
|             if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) { |                 if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) { | ||||||
|                 locked_view->lock_lastpress = press_time; |  | ||||||
|                 locked_view->lock_count = 0; |  | ||||||
|             } else if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) { |  | ||||||
|                     locked_view->lock_lastpress = press_time; |                     locked_view->lock_lastpress = press_time; | ||||||
|                     locked_view->lock_count++; |                     locked_view->lock_count++; | ||||||
|                 } |                 } | ||||||
| @ -138,6 +162,12 @@ bool desktop_locked_input(InputEvent* event, void* context) { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) { | ||||||
|  |             locked_view->lock_lastpress = press_time; | ||||||
|  |             locked_view->lock_count = 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|     // All events consumed
 |     // All events consumed
 | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
|  | |||||||
| @ -15,10 +15,16 @@ | |||||||
| #define DOOR_R_POS_MIN 60 | #define DOOR_R_POS_MIN 60 | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     DesktopLockedEventUnlock, |     DesktopLockedEventUnlock = 10U, | ||||||
|     DesktopLockedEventUpdate, |     DesktopLockedEventUpdate = 11U, | ||||||
|  |     DesktopLockedEventInputReset = 12U, | ||||||
| } DesktopLockedEvent; | } DesktopLockedEvent; | ||||||
| 
 | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopLockedWithPin, | ||||||
|  |     DesktopLockedNoPin, | ||||||
|  | } DesktopLockedSceneState; | ||||||
|  | 
 | ||||||
| typedef struct DesktopLockedView DesktopLockedView; | typedef struct DesktopLockedView DesktopLockedView; | ||||||
| 
 | 
 | ||||||
| typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context); | typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context); | ||||||
| @ -42,6 +48,7 @@ typedef struct { | |||||||
|     int8_t door_right_x; |     int8_t door_right_x; | ||||||
|     bool animation_seq_end; |     bool animation_seq_end; | ||||||
| 
 | 
 | ||||||
|  |     bool pin_lock; | ||||||
| } DesktopLockedViewModel; | } DesktopLockedViewModel; | ||||||
| 
 | 
 | ||||||
| void desktop_locked_set_callback( | void desktop_locked_set_callback( | ||||||
| @ -58,5 +65,4 @@ void desktop_locked_manage_redraw(DesktopLockedView* locked_view); | |||||||
| View* desktop_locked_get_view(DesktopLockedView* locked_view); | View* desktop_locked_get_view(DesktopLockedView* locked_view); | ||||||
| DesktopLockedView* desktop_locked_alloc(); | DesktopLockedView* desktop_locked_alloc(); | ||||||
| void desktop_locked_free(DesktopLockedView* locked_view); | void desktop_locked_free(DesktopLockedView* locked_view); | ||||||
| void desktop_main_unlocked(DesktopMainView* main_view); | void desktop_locked_with_pin(DesktopLockedView* lock_menu, bool locked); | ||||||
| void desktop_main_reset_hint(DesktopMainView* main_view); |  | ||||||
| @ -67,6 +67,7 @@ bool desktop_main_input(InputEvent* event, void* context) { | |||||||
|     } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { |     } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { | ||||||
|         main_view->callback(DesktopMainEventOpenFavorite, main_view->context); |         main_view->callback(DesktopMainEventOpenFavorite, main_view->context); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     desktop_main_reset_hint(main_view); |     desktop_main_reset_hint(main_view); | ||||||
| 
 | 
 | ||||||
|     return true; |     return true; | ||||||
|  | |||||||
| @ -7,12 +7,12 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     DesktopMainEventOpenMenu, |  | ||||||
|     DesktopMainEventOpenLockMenu, |     DesktopMainEventOpenLockMenu, | ||||||
|     DesktopMainEventOpenDebug, |  | ||||||
|     DesktopMainEventUnlocked, |  | ||||||
|     DesktopMainEventOpenArchive, |     DesktopMainEventOpenArchive, | ||||||
|     DesktopMainEventOpenFavorite, |     DesktopMainEventOpenFavorite, | ||||||
|  |     DesktopMainEventOpenMenu, | ||||||
|  |     DesktopMainEventOpenDebug, | ||||||
|  |     DesktopMainEventUnlocked, | ||||||
| } DesktopMainEvent; | } DesktopMainEvent; | ||||||
| 
 | 
 | ||||||
| typedef struct DesktopMainView DesktopMainView; | typedef struct DesktopMainView DesktopMainView; | ||||||
| @ -37,9 +37,8 @@ void desktop_main_set_callback( | |||||||
|     void* context); |     void* context); | ||||||
| 
 | 
 | ||||||
| View* desktop_main_get_view(DesktopMainView* main_view); | View* desktop_main_get_view(DesktopMainView* main_view); | ||||||
| 
 |  | ||||||
| DesktopMainView* desktop_main_alloc(); | DesktopMainView* desktop_main_alloc(); | ||||||
| 
 |  | ||||||
| void desktop_main_free(DesktopMainView* main_view); | void desktop_main_free(DesktopMainView* main_view); | ||||||
| 
 |  | ||||||
| void desktop_main_switch_dolphin_animation(DesktopMainView* main_view); | void desktop_main_switch_dolphin_animation(DesktopMainView* main_view); | ||||||
|  | void desktop_main_unlocked(DesktopMainView* main_view); | ||||||
|  | void desktop_main_reset_hint(DesktopMainView* main_view); | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| #include "view-holder.h" | #include "view-holder.h" | ||||||
| #include <gui/view_i.h> | #include <gui/view_i.h> | ||||||
| 
 | 
 | ||||||
|  | #define TAG "ViewHolder" | ||||||
|  | 
 | ||||||
| struct ViewHolder { | struct ViewHolder { | ||||||
|     View* view; |     View* view; | ||||||
|     ViewPort* view_port; |     ViewPort* view_port; | ||||||
| @ -125,7 +127,7 @@ static void view_holder_input_callback(InputEvent* event, void* context) { | |||||||
|         view_holder->ongoing_input &= ~key_bit; |         view_holder->ongoing_input &= ~key_bit; | ||||||
|     } else if(!(view_holder->ongoing_input & key_bit)) { |     } else if(!(view_holder->ongoing_input & key_bit)) { | ||||||
|         FURI_LOG_W( |         FURI_LOG_W( | ||||||
|             "ViewHolder", |             TAG, | ||||||
|             "non-complementary input, discarding key: %s, type: %s", |             "non-complementary input, discarding key: %s, type: %s", | ||||||
|             input_get_key_name(event->key), |             input_get_key_name(event->key), | ||||||
|             input_get_type_name(event->type)); |             input_get_type_name(event->type)); | ||||||
|  | |||||||
| @ -81,13 +81,8 @@ static void dolphin_check_butthurt(DolphinState* state) { | |||||||
|     furi_assert(state); |     furi_assert(state); | ||||||
|     float diff_time = difftime(dolphin_state_get_timestamp(state), dolphin_state_timestamp()); |     float diff_time = difftime(dolphin_state_get_timestamp(state), dolphin_state_timestamp()); | ||||||
| 
 | 
 | ||||||
| #if 0 |  | ||||||
|     FURI_LOG_I("dolphin-state", "Butthurt check, time since deed %.0f", fabs(diff_time)); |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
|     if((fabs(diff_time)) > DOLPHIN_TIMEGATE) { |     if((fabs(diff_time)) > DOLPHIN_TIMEGATE) { | ||||||
|         // increase butthurt
 |         FURI_LOG_I("DolphinState", "Increasing butthurt"); | ||||||
|         FURI_LOG_I("dolphin-state", "Increasing butthurt"); |  | ||||||
|         dolphin_state_butthurted(state); |         dolphin_state_butthurted(state); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,20 +2,14 @@ | |||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <math.h> | #include <math.h> | ||||||
|  | #include <toolbox/saved_struct.h> | ||||||
| 
 | 
 | ||||||
| #define DOLPHIN_STORE_KEY "/int/dolphin.state" | #define TAG "DolphinState" | ||||||
| #define DOLPHIN_STORE_HEADER_MAGIC 0xD0 | #define DOLPHIN_STATE_PATH "/int/dolphin.state" | ||||||
| #define DOLPHIN_STORE_HEADER_VERSION 0x01 | #define DOLPHIN_STATE_HEADER_MAGIC 0xD0 | ||||||
|  | #define DOLPHIN_STATE_HEADER_VERSION 0x01 | ||||||
| #define DOLPHIN_LVL_THRESHOLD 20.0f | #define DOLPHIN_LVL_THRESHOLD 20.0f | ||||||
| 
 | 
 | ||||||
| typedef struct { |  | ||||||
|     uint8_t magic; |  | ||||||
|     uint8_t version; |  | ||||||
|     uint8_t checksum; |  | ||||||
|     uint8_t flags; |  | ||||||
|     uint32_t timestamp; |  | ||||||
| } DolphinStoreHeader; |  | ||||||
| 
 |  | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint32_t limit_ibutton; |     uint32_t limit_ibutton; | ||||||
|     uint32_t limit_nfc; |     uint32_t limit_nfc; | ||||||
| @ -28,25 +22,16 @@ typedef struct { | |||||||
|     uint64_t timestamp; |     uint64_t timestamp; | ||||||
| } DolphinStoreData; | } DolphinStoreData; | ||||||
| 
 | 
 | ||||||
| typedef struct { |  | ||||||
|     DolphinStoreHeader header; |  | ||||||
|     DolphinStoreData data; |  | ||||||
| } DolphinStore; |  | ||||||
| 
 |  | ||||||
| struct DolphinState { | struct DolphinState { | ||||||
|     Storage* fs_api; |  | ||||||
|     DolphinStoreData data; |     DolphinStoreData data; | ||||||
|     bool dirty; |     bool dirty; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| DolphinState* dolphin_state_alloc() { | DolphinState* dolphin_state_alloc() { | ||||||
|     DolphinState* dolphin_state = furi_alloc(sizeof(DolphinState)); |     return furi_alloc(sizeof(DolphinState)); | ||||||
|     dolphin_state->fs_api = furi_record_open("storage"); |  | ||||||
|     return dolphin_state; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void dolphin_state_free(DolphinState* dolphin_state) { | void dolphin_state_free(DolphinState* dolphin_state) { | ||||||
|     furi_record_close("storage"); |  | ||||||
|     free(dolphin_state); |     free(dolphin_state); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -55,121 +40,38 @@ bool dolphin_state_save(DolphinState* dolphin_state) { | |||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I("dolphin-state", "State is dirty, saving to \"%s\"", DOLPHIN_STORE_KEY); |     bool result = saved_struct_save( | ||||||
|     DolphinStore store; |         DOLPHIN_STATE_PATH, | ||||||
|     // Calculate checksum
 |         &dolphin_state->data, | ||||||
|     uint8_t* source = (uint8_t*)&dolphin_state->data; |         sizeof(DolphinStoreData), | ||||||
|     uint8_t checksum = 0; |         DOLPHIN_STATE_HEADER_MAGIC, | ||||||
|     for(size_t i = 0; i < sizeof(DolphinStoreData); i++) { |         DOLPHIN_STATE_HEADER_VERSION); | ||||||
|         checksum += source[i]; |  | ||||||
|     } |  | ||||||
|     // Set header
 |  | ||||||
|     store.header.magic = DOLPHIN_STORE_HEADER_MAGIC; |  | ||||||
|     store.header.version = DOLPHIN_STORE_HEADER_VERSION; |  | ||||||
|     store.header.checksum = checksum; |  | ||||||
|     store.header.flags = 0; |  | ||||||
|     store.header.timestamp = 0; |  | ||||||
|     // Set data
 |  | ||||||
|     store.data = dolphin_state->data; |  | ||||||
| 
 | 
 | ||||||
|     // Store
 |     if(result) { | ||||||
|     File* file = storage_file_alloc(dolphin_state->fs_api); |         FURI_LOG_I(TAG, "State saved"); | ||||||
|     bool save_result = storage_file_open(file, DOLPHIN_STORE_KEY, FSAM_WRITE, FSOM_CREATE_ALWAYS); |         dolphin_state->dirty = false; | ||||||
| 
 |     } else { | ||||||
|     if(save_result) { |         FURI_LOG_E(TAG, "Failed to save state"); | ||||||
|         uint16_t bytes_count = storage_file_write(file, &store, sizeof(DolphinStore)); |  | ||||||
| 
 |  | ||||||
|         if(bytes_count != sizeof(DolphinStore)) { |  | ||||||
|             save_result = false; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(!save_result) { |     return result; | ||||||
|         FURI_LOG_E( |  | ||||||
|             "dolphin-state", |  | ||||||
|             "Save failed. Storage returned: %s", |  | ||||||
|             storage_file_get_error_desc(file)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     storage_file_close(file); |  | ||||||
|     storage_file_free(file); |  | ||||||
| 
 |  | ||||||
|     dolphin_state->dirty = !save_result; |  | ||||||
| 
 |  | ||||||
|     FURI_LOG_I("dolphin-state", "Saved"); |  | ||||||
| 
 |  | ||||||
|     return save_result; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool dolphin_state_load(DolphinState* dolphin_state) { | bool dolphin_state_load(DolphinState* dolphin_state) { | ||||||
|     DolphinStore store; |     bool loaded = saved_struct_load( | ||||||
|     // Read Dolphin State Store
 |         DOLPHIN_STATE_PATH, | ||||||
|     FURI_LOG_I("dolphin-state", "Loading state from \"%s\"", DOLPHIN_STORE_KEY); |         &dolphin_state->data, | ||||||
|  |         sizeof(DolphinStoreData), | ||||||
|  |         DOLPHIN_STATE_HEADER_MAGIC, | ||||||
|  |         DOLPHIN_STATE_HEADER_VERSION); | ||||||
| 
 | 
 | ||||||
|     File* file = storage_file_alloc(dolphin_state->fs_api); |     if(!loaded) { | ||||||
|     bool load_result = storage_file_open(file, DOLPHIN_STORE_KEY, FSAM_READ, FSOM_OPEN_EXISTING); |         FURI_LOG_W(TAG, "Reset dolphin-state"); | ||||||
|     if(!load_result) { |         memset(dolphin_state, 0, sizeof(*dolphin_state)); | ||||||
|         FURI_LOG_E( |         dolphin_state->dirty = true; | ||||||
|             "dolphin-state", |  | ||||||
|             "Load failed. Storage returned: %s", |  | ||||||
|             storage_file_get_error_desc(file)); |  | ||||||
|     } else { |  | ||||||
|         uint16_t bytes_count = storage_file_read(file, &store, sizeof(DolphinStore)); |  | ||||||
| 
 |  | ||||||
|         if(bytes_count != sizeof(DolphinStore)) { |  | ||||||
|             load_result = false; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(!load_result) { |     return loaded; | ||||||
|         FURI_LOG_E("dolphin-state", "DolphinStore size mismatch"); |  | ||||||
|     } else { |  | ||||||
|         if(store.header.magic == DOLPHIN_STORE_HEADER_MAGIC && |  | ||||||
|            store.header.version == DOLPHIN_STORE_HEADER_VERSION) { |  | ||||||
|             FURI_LOG_I( |  | ||||||
|                 "dolphin-state", |  | ||||||
|                 "Magic(%d) and Version(%d) match", |  | ||||||
|                 store.header.magic, |  | ||||||
|                 store.header.version); |  | ||||||
|             uint8_t checksum = 0; |  | ||||||
|             const uint8_t* source = (const uint8_t*)&store.data; |  | ||||||
|             for(size_t i = 0; i < sizeof(DolphinStoreData); i++) { |  | ||||||
|                 checksum += source[i]; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if(store.header.checksum == checksum) { |  | ||||||
|                 FURI_LOG_I("dolphin-state", "Checksum(%d) match", store.header.checksum); |  | ||||||
|                 dolphin_state->data = store.data; |  | ||||||
|             } else { |  | ||||||
|                 FURI_LOG_E( |  | ||||||
|                     "dolphin-state", |  | ||||||
|                     "Checksum(%d != %d) mismatch", |  | ||||||
|                     store.header.checksum, |  | ||||||
|                     checksum); |  | ||||||
|                 load_result = false; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             FURI_LOG_E( |  | ||||||
|                 "dolphin-state", |  | ||||||
|                 "Magic(%d != %d) or Version(%d != %d) mismatch", |  | ||||||
|                 store.header.magic, |  | ||||||
|                 DOLPHIN_STORE_HEADER_MAGIC, |  | ||||||
|                 store.header.version, |  | ||||||
|                 DOLPHIN_STORE_HEADER_VERSION); |  | ||||||
|             load_result = false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     storage_file_close(file); |  | ||||||
|     storage_file_free(file); |  | ||||||
| 
 |  | ||||||
|     dolphin_state->dirty = !load_result; |  | ||||||
| 
 |  | ||||||
|     return load_result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void dolphin_state_clear(DolphinState* dolphin_state) { |  | ||||||
|     memset(&dolphin_state->data, 0, sizeof(DolphinStoreData)); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| uint64_t dolphin_state_timestamp() { | uint64_t dolphin_state_timestamp() { | ||||||
|  | |||||||
| @ -12,6 +12,14 @@ | |||||||
| #include <gui/modules/variable-item-list.h> | #include <gui/modules/variable-item-list.h> | ||||||
| #include "views/gpio_test.h" | #include "views/gpio_test.h" | ||||||
| 
 | 
 | ||||||
|  | #define GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF (0UL) | ||||||
|  | #define GPIO_SCENE_START_CUSTOM_EVENT_OTG_ON (1UL) | ||||||
|  | #define GPIO_SCENE_START_CUSTOM_EVENT_TEST (2UL) | ||||||
|  | #define GPIO_SCENE_START_CUSTOM_EVENT_USB_UART (3UL) | ||||||
|  | 
 | ||||||
|  | #define GPIO_SCENE_USB_UART_CUSTOM_EVENT_ENABLE (4UL) | ||||||
|  | #define GPIO_SCENE_USB_UART_CUSTOM_EVENT_DISABLE (5UL) | ||||||
|  | 
 | ||||||
| struct GpioApp { | struct GpioApp { | ||||||
|     Gui* gui; |     Gui* gui; | ||||||
|     ViewDispatcher* view_dispatcher; |     ViewDispatcher* view_dispatcher; | ||||||
|  | |||||||
| @ -1,11 +1,6 @@ | |||||||
| #include "../gpio_app_i.h" | #include "../gpio_app_i.h" | ||||||
| #include "furi-hal-power.h" | #include "furi-hal-power.h" | ||||||
| 
 | 
 | ||||||
| #define GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF (0UL) |  | ||||||
| #define GPIO_SCENE_START_CUSTOM_EVENT_OTG_ON (1UL) |  | ||||||
| #define GPIO_SCENE_START_CUSTOM_EVENT_TEST (2UL) |  | ||||||
| #define GPIO_SCENE_START_CUSTOM_EVENT_USB_UART (3UL) |  | ||||||
| 
 |  | ||||||
| enum GpioItem { | enum GpioItem { | ||||||
|     GpioItemOtg, |     GpioItemOtg, | ||||||
|     GpioItemTest, |     GpioItemTest, | ||||||
| @ -72,6 +67,9 @@ void gpio_scene_start_on_enter(void* context) { | |||||||
|     variable_item_list_add(var_item_list, "GPIO tester", 0, NULL, NULL); |     variable_item_list_add(var_item_list, "GPIO tester", 0, NULL, NULL); | ||||||
|     variable_item_list_add(var_item_list, "USB-UART bridge", 0, NULL, NULL); |     variable_item_list_add(var_item_list, "USB-UART bridge", 0, NULL, NULL); | ||||||
| 
 | 
 | ||||||
|  |     variable_item_list_set_selected_item( | ||||||
|  |         var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioSceneStart)); | ||||||
|  | 
 | ||||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewVarItemList); |     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewVarItemList); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -85,8 +83,10 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { | |||||||
|         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF) { |         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF) { | ||||||
|             furi_hal_power_disable_otg(); |             furi_hal_power_disable_otg(); | ||||||
|         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_TEST) { |         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_TEST) { | ||||||
|  |             scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, 1); | ||||||
|             scene_manager_next_scene(app->scene_manager, GpioSceneTest); |             scene_manager_next_scene(app->scene_manager, GpioSceneTest); | ||||||
|         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_USB_UART) { |         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_USB_UART) { | ||||||
|  |             scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, 2); | ||||||
|             scene_manager_next_scene(app->scene_manager, GpioSceneUsbUart); |             scene_manager_next_scene(app->scene_manager, GpioSceneUsbUart); | ||||||
|         } |         } | ||||||
|         consumed = true; |         consumed = true; | ||||||
|  | |||||||
| @ -1,17 +1,6 @@ | |||||||
|  | #include "../usb_uart_bridge.h" | ||||||
| #include "../gpio_app_i.h" | #include "../gpio_app_i.h" | ||||||
| #include "furi-hal.h" | #include "furi-hal.h" | ||||||
| #include <stream_buffer.h> |  | ||||||
| #include <furi-hal-usb-cdc_i.h> |  | ||||||
| #include "usb_cdc.h" |  | ||||||
| 
 |  | ||||||
| #define USB_PKT_LEN CDC_DATA_SZ |  | ||||||
| #define USB_UART_RX_BUF_SIZE (USB_PKT_LEN * 3) |  | ||||||
| #define USB_UART_TX_BUF_SIZE (USB_PKT_LEN * 3) |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     WorkerCmdStop = (1 << 0), |  | ||||||
| 
 |  | ||||||
| } WorkerCommandFlags; |  | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     UsbUartLineIndexVcp, |     UsbUartLineIndexVcp, | ||||||
| @ -21,42 +10,7 @@ typedef enum { | |||||||
|     UsbUartLineIndexDisable, |     UsbUartLineIndexDisable, | ||||||
| } LineIndex; | } LineIndex; | ||||||
| 
 | 
 | ||||||
| typedef enum { | static UsbUartConfig* cfg_set; | ||||||
|     UsbUartPortUSART1 = 0, |  | ||||||
|     UsbUartPortLPUART1 = 1, |  | ||||||
| } PortIdx; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     uint8_t vcp_ch; |  | ||||||
|     PortIdx uart_ch; |  | ||||||
|     uint32_t baudrate; |  | ||||||
| } UsbUartConfig; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     UsbUartConfig cfg_cur; |  | ||||||
|     UsbUartConfig cfg_set; |  | ||||||
|     char br_text[8]; |  | ||||||
| 
 |  | ||||||
|     bool running; |  | ||||||
|     osThreadId_t parent_thread; |  | ||||||
| 
 |  | ||||||
|     osThreadAttr_t thread_attr; |  | ||||||
|     osThreadId_t thread; |  | ||||||
| 
 |  | ||||||
|     osThreadAttr_t tx_thread_attr; |  | ||||||
|     osThreadId_t tx_thread; |  | ||||||
| 
 |  | ||||||
|     StreamBufferHandle_t rx_stream; |  | ||||||
|     osSemaphoreId_t rx_done_sem; |  | ||||||
|     osSemaphoreId_t usb_sof_sem; |  | ||||||
| 
 |  | ||||||
|     StreamBufferHandle_t tx_stream; |  | ||||||
| 
 |  | ||||||
|     uint8_t rx_buf[USB_PKT_LEN]; |  | ||||||
|     uint8_t tx_buf[USB_PKT_LEN]; |  | ||||||
| } UsbUartParams; |  | ||||||
| 
 |  | ||||||
| static UsbUartParams* usb_uart; |  | ||||||
| 
 | 
 | ||||||
| static const char* vcp_ch[] = {"0 (CLI)", "1"}; | static const char* vcp_ch[] = {"0 (CLI)", "1"}; | ||||||
| static const char* uart_ch[] = {"USART1", "LPUART1"}; | static const char* uart_ch[] = {"USART1", "LPUART1"}; | ||||||
| @ -73,197 +27,14 @@ static const uint32_t baudrate_list[] = { | |||||||
|     921600, |     921600, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static void vcp_on_cdc_tx_complete(); |  | ||||||
| static void vcp_on_cdc_rx(); |  | ||||||
| static void vcp_state_callback(uint8_t state); |  | ||||||
| static void vcp_on_cdc_control_line(uint8_t state); |  | ||||||
| static void vcp_on_line_config(struct usb_cdc_line_coding* config); |  | ||||||
| 
 |  | ||||||
| static CdcCallbacks cdc_cb = { |  | ||||||
|     vcp_on_cdc_tx_complete, |  | ||||||
|     vcp_on_cdc_rx, |  | ||||||
|     vcp_state_callback, |  | ||||||
|     vcp_on_cdc_control_line, |  | ||||||
|     vcp_on_line_config, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /* USB UART worker */ |  | ||||||
| 
 |  | ||||||
| static void usb_uart_tx_thread(void* context); |  | ||||||
| 
 |  | ||||||
| static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data) { |  | ||||||
|     BaseType_t xHigherPriorityTaskWoken = pdFALSE; |  | ||||||
| 
 |  | ||||||
|     if(ev == UartIrqEventRXNE) { |  | ||||||
|         size_t ret = |  | ||||||
|             xStreamBufferSendFromISR(usb_uart->rx_stream, &data, 1, &xHigherPriorityTaskWoken); |  | ||||||
|         furi_check(ret == 1); |  | ||||||
|         ret = xStreamBufferBytesAvailable(usb_uart->rx_stream); |  | ||||||
|         if(ret > USB_PKT_LEN) osSemaphoreRelease(usb_uart->rx_done_sem); |  | ||||||
|     } else if(ev == UartIrqEventIDLE) { |  | ||||||
|         osSemaphoreRelease(usb_uart->rx_done_sem); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     portYIELD_FROM_ISR(xHigherPriorityTaskWoken); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void usb_uart_worker(void* context) { |  | ||||||
|     memcpy(&usb_uart->cfg_cur, &usb_uart->cfg_set, sizeof(UsbUartConfig)); |  | ||||||
| 
 |  | ||||||
|     usb_uart->rx_stream = xStreamBufferCreate(USB_UART_RX_BUF_SIZE, 1); |  | ||||||
|     usb_uart->rx_done_sem = osSemaphoreNew(1, 1, NULL); |  | ||||||
|     usb_uart->usb_sof_sem = osSemaphoreNew(1, 1, NULL); |  | ||||||
| 
 |  | ||||||
|     usb_uart->tx_stream = xStreamBufferCreate(USB_UART_TX_BUF_SIZE, 1); |  | ||||||
| 
 |  | ||||||
|     usb_uart->tx_thread = NULL; |  | ||||||
|     usb_uart->tx_thread_attr.name = "usb_uart_tx"; |  | ||||||
|     usb_uart->tx_thread_attr.stack_size = 512; |  | ||||||
| 
 |  | ||||||
|     UsbMode usb_mode_prev = furi_hal_usb_get_config(); |  | ||||||
|     if(usb_uart->cfg_cur.vcp_ch == 0) { |  | ||||||
|         furi_hal_usb_set_config(UsbModeVcpSingle); |  | ||||||
|         furi_hal_vcp_disable(); |  | ||||||
|     } else { |  | ||||||
|         furi_hal_usb_set_config(UsbModeVcpDual); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(usb_uart->cfg_cur.uart_ch == UsbUartPortUSART1) { |  | ||||||
|         furi_hal_usart_init(); |  | ||||||
|         furi_hal_usart_set_irq_cb(usb_uart_on_irq_cb); |  | ||||||
|         if(usb_uart->cfg_cur.baudrate != 0) |  | ||||||
|             furi_hal_usart_set_br(usb_uart->cfg_cur.baudrate); |  | ||||||
|         else |  | ||||||
|             vcp_on_line_config(furi_hal_cdc_get_port_settings(usb_uart->cfg_cur.vcp_ch)); |  | ||||||
|     } else if(usb_uart->cfg_cur.uart_ch == UsbUartPortLPUART1) { |  | ||||||
|         furi_hal_lpuart_init(); |  | ||||||
|         furi_hal_lpuart_set_irq_cb(usb_uart_on_irq_cb); |  | ||||||
|         if(usb_uart->cfg_cur.baudrate != 0) |  | ||||||
|             furi_hal_lpuart_set_br(usb_uart->cfg_cur.baudrate); |  | ||||||
|         else |  | ||||||
|             vcp_on_line_config(furi_hal_cdc_get_port_settings(usb_uart->cfg_cur.vcp_ch)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     furi_hal_cdc_set_callbacks(usb_uart->cfg_cur.vcp_ch, &cdc_cb); |  | ||||||
|     usb_uart->tx_thread = osThreadNew(usb_uart_tx_thread, NULL, &usb_uart->tx_thread_attr); |  | ||||||
| 
 |  | ||||||
|     while(1) { |  | ||||||
|         furi_check(osSemaphoreAcquire(usb_uart->rx_done_sem, osWaitForever) == osOK); |  | ||||||
|         if(osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny, 0) == WorkerCmdStop) break; |  | ||||||
|         size_t len = 0; |  | ||||||
|         do { |  | ||||||
|             len = xStreamBufferReceive(usb_uart->rx_stream, usb_uart->rx_buf, USB_PKT_LEN, 0); |  | ||||||
|             if(len > 0) { |  | ||||||
|                 if(osSemaphoreAcquire(usb_uart->usb_sof_sem, 100) == osOK) |  | ||||||
|                     furi_hal_cdc_send(usb_uart->cfg_cur.vcp_ch, usb_uart->rx_buf, len); |  | ||||||
|                 else |  | ||||||
|                     xStreamBufferReset(usb_uart->rx_stream); |  | ||||||
|             } |  | ||||||
|         } while(len > 0); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     osThreadTerminate(usb_uart->tx_thread); |  | ||||||
| 
 |  | ||||||
|     if(usb_uart->cfg_cur.uart_ch == UsbUartPortUSART1) |  | ||||||
|         furi_hal_usart_deinit(); |  | ||||||
|     else if(usb_uart->cfg_cur.uart_ch == UsbUartPortLPUART1) |  | ||||||
|         furi_hal_lpuart_deinit(); |  | ||||||
| 
 |  | ||||||
|     furi_hal_cdc_set_callbacks(usb_uart->cfg_cur.vcp_ch, NULL); |  | ||||||
|     furi_hal_usb_set_config(usb_mode_prev); |  | ||||||
|     if(usb_uart->cfg_cur.vcp_ch == 0) furi_hal_vcp_enable(); |  | ||||||
| 
 |  | ||||||
|     vStreamBufferDelete(usb_uart->rx_stream); |  | ||||||
|     osSemaphoreDelete(usb_uart->rx_done_sem); |  | ||||||
|     osSemaphoreDelete(usb_uart->usb_sof_sem); |  | ||||||
| 
 |  | ||||||
|     vStreamBufferDelete(usb_uart->tx_stream); |  | ||||||
|     osThreadFlagsSet(usb_uart->parent_thread, WorkerCmdStop); |  | ||||||
|     osThreadExit(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void usb_uart_tx_thread(void* context) { |  | ||||||
|     uint8_t data = 0; |  | ||||||
|     while(1) { |  | ||||||
|         size_t len = xStreamBufferReceive(usb_uart->tx_stream, &data, 1, osWaitForever); |  | ||||||
|         if(len > 0) { |  | ||||||
|             if(usb_uart->cfg_cur.uart_ch == UsbUartPortUSART1) |  | ||||||
|                 furi_hal_usart_tx(&data, len); |  | ||||||
|             else if(usb_uart->cfg_cur.uart_ch == UsbUartPortLPUART1) |  | ||||||
|                 furi_hal_lpuart_tx(&data, len); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     osThreadExit(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* VCP callbacks */ |  | ||||||
| 
 |  | ||||||
| static void vcp_on_cdc_tx_complete() { |  | ||||||
|     osSemaphoreRelease(usb_uart->usb_sof_sem); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void vcp_on_cdc_rx() { |  | ||||||
|     BaseType_t xHigherPriorityTaskWoken = pdFALSE; |  | ||||||
| 
 |  | ||||||
|     uint16_t max_len = xStreamBufferSpacesAvailable(usb_uart->tx_stream); |  | ||||||
|     if(max_len > 0) { |  | ||||||
|         if(max_len > USB_PKT_LEN) max_len = USB_PKT_LEN; |  | ||||||
|         int32_t size = furi_hal_cdc_receive(usb_uart->cfg_cur.vcp_ch, usb_uart->tx_buf, max_len); |  | ||||||
| 
 |  | ||||||
|         if(size > 0) { |  | ||||||
|             size_t ret = xStreamBufferSendFromISR( |  | ||||||
|                 usb_uart->tx_stream, usb_uart->tx_buf, size, &xHigherPriorityTaskWoken); |  | ||||||
|             furi_check(ret == size); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     portYIELD_FROM_ISR(xHigherPriorityTaskWoken); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void vcp_state_callback(uint8_t state) { |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void vcp_on_cdc_control_line(uint8_t state) { |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void vcp_on_line_config(struct usb_cdc_line_coding* config) { |  | ||||||
|     if((usb_uart->cfg_cur.baudrate == 0) && (config->dwDTERate != 0)) { |  | ||||||
|         if(usb_uart->cfg_cur.uart_ch == UsbUartPortUSART1) |  | ||||||
|             furi_hal_usart_set_br(config->dwDTERate); |  | ||||||
|         else if(usb_uart->cfg_cur.uart_ch == UsbUartPortLPUART1) |  | ||||||
|             furi_hal_lpuart_set_br(config->dwDTERate); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* USB UART app */ |  | ||||||
| 
 |  | ||||||
| static void usb_uart_enable() { |  | ||||||
|     if(usb_uart->running == false) { |  | ||||||
|         usb_uart->thread = NULL; |  | ||||||
|         usb_uart->thread_attr.name = "usb_uart"; |  | ||||||
|         usb_uart->thread_attr.stack_size = 1024; |  | ||||||
|         usb_uart->parent_thread = osThreadGetId(); |  | ||||||
|         usb_uart->running = true; |  | ||||||
|         usb_uart->thread = osThreadNew(usb_uart_worker, NULL, &usb_uart->thread_attr); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void usb_uart_disable() { |  | ||||||
|     if(usb_uart->running == true) { |  | ||||||
|         osThreadFlagsSet(usb_uart->thread, WorkerCmdStop); |  | ||||||
|         osSemaphoreRelease(usb_uart->rx_done_sem); |  | ||||||
|         osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny, osWaitForever); |  | ||||||
|         usb_uart->running = false; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { | bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { | ||||||
|     //GpioApp* app = context;
 |     //GpioApp* app = context;
 | ||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == UsbUartLineIndexEnable) { |         if(event.event == GPIO_SCENE_USB_UART_CUSTOM_EVENT_ENABLE) { | ||||||
|             usb_uart_enable(); |             usb_uart_enable(cfg_set); | ||||||
|         } else if(event.event == UsbUartLineIndexDisable) { |         } else if(event.event == GPIO_SCENE_USB_UART_CUSTOM_EVENT_DISABLE) { | ||||||
|             usb_uart_disable(); |             usb_uart_disable(); | ||||||
|         } |         } | ||||||
|         consumed = true; |         consumed = true; | ||||||
| @ -271,15 +42,13 @@ bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { | |||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Scene callbacks */ |  | ||||||
| 
 |  | ||||||
| static void line_vcp_cb(VariableItem* item) { | static void line_vcp_cb(VariableItem* item) { | ||||||
|     //GpioApp* app = variable_item_get_context(item);
 |     //GpioApp* app = variable_item_get_context(item);
 | ||||||
|     uint8_t index = variable_item_get_current_value_index(item); |     uint8_t index = variable_item_get_current_value_index(item); | ||||||
| 
 | 
 | ||||||
|     variable_item_set_current_value_text(item, vcp_ch[index]); |     variable_item_set_current_value_text(item, vcp_ch[index]); | ||||||
| 
 | 
 | ||||||
|     usb_uart->cfg_set.vcp_ch = index; |     cfg_set->vcp_ch = index; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void line_port_cb(VariableItem* item) { | static void line_port_cb(VariableItem* item) { | ||||||
| @ -288,34 +57,44 @@ static void line_port_cb(VariableItem* item) { | |||||||
| 
 | 
 | ||||||
|     variable_item_set_current_value_text(item, uart_ch[index]); |     variable_item_set_current_value_text(item, uart_ch[index]); | ||||||
| 
 | 
 | ||||||
|     usb_uart->cfg_set.uart_ch = index; |     if(index == 0) | ||||||
|  |         cfg_set->uart_ch = FuriHalUartIdUSART1; | ||||||
|  |     else if(index == 1) | ||||||
|  |         cfg_set->uart_ch = FuriHalUartIdLPUART1; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void line_baudrate_cb(VariableItem* item) { | static void line_baudrate_cb(VariableItem* item) { | ||||||
|     //GpioApp* app = variable_item_get_context(item);
 |     //GpioApp* app = variable_item_get_context(item);
 | ||||||
|     uint8_t index = variable_item_get_current_value_index(item); |     uint8_t index = variable_item_get_current_value_index(item); | ||||||
| 
 | 
 | ||||||
|  |     char br_text[8]; | ||||||
|  | 
 | ||||||
|     if(index > 0) { |     if(index > 0) { | ||||||
|         snprintf(usb_uart->br_text, 7, "%lu", baudrate_list[index - 1]); |         snprintf(br_text, 7, "%lu", baudrate_list[index - 1]); | ||||||
|         variable_item_set_current_value_text(item, usb_uart->br_text); |         variable_item_set_current_value_text(item, br_text); | ||||||
|         usb_uart->cfg_set.baudrate = baudrate_list[index - 1]; |         cfg_set->baudrate = baudrate_list[index - 1]; | ||||||
|     } else { |     } else { | ||||||
|         variable_item_set_current_value_text(item, baudrate_mode[index]); |         variable_item_set_current_value_text(item, baudrate_mode[index]); | ||||||
|         usb_uart->cfg_set.baudrate = 0; |         cfg_set->baudrate = 0; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void gpio_scene_usb_uart_enter_callback(void* context, uint32_t index) { | static void gpio_scene_usb_uart_enter_callback(void* context, uint32_t index) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     GpioApp* app = context; |     GpioApp* app = context; | ||||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, index); |     if(index == UsbUartLineIndexEnable) | ||||||
|  |         view_dispatcher_send_custom_event( | ||||||
|  |             app->view_dispatcher, GPIO_SCENE_USB_UART_CUSTOM_EVENT_ENABLE); | ||||||
|  |     else if(index == UsbUartLineIndexDisable) | ||||||
|  |         view_dispatcher_send_custom_event( | ||||||
|  |             app->view_dispatcher, GPIO_SCENE_USB_UART_CUSTOM_EVENT_DISABLE); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void gpio_scene_usb_uart_on_enter(void* context) { | void gpio_scene_usb_uart_on_enter(void* context) { | ||||||
|     GpioApp* app = context; |     GpioApp* app = context; | ||||||
|     VariableItemList* var_item_list = app->var_item_list; |     VariableItemList* var_item_list = app->var_item_list; | ||||||
| 
 | 
 | ||||||
|     usb_uart = furi_alloc(sizeof(UsbUartParams)); |     cfg_set = furi_alloc(sizeof(UsbUartConfig)); | ||||||
| 
 | 
 | ||||||
|     VariableItem* item; |     VariableItem* item; | ||||||
| 
 | 
 | ||||||
| @ -341,12 +120,19 @@ void gpio_scene_usb_uart_on_enter(void* context) { | |||||||
|     item = variable_item_list_add(var_item_list, "Enable", 0, NULL, NULL); |     item = variable_item_list_add(var_item_list, "Enable", 0, NULL, NULL); | ||||||
|     item = variable_item_list_add(var_item_list, "Disable", 0, NULL, NULL); |     item = variable_item_list_add(var_item_list, "Disable", 0, NULL, NULL); | ||||||
| 
 | 
 | ||||||
|  |     variable_item_list_set_selected_item( | ||||||
|  |         var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioSceneUsbUart)); | ||||||
|  | 
 | ||||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUart); |     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUart); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void gpio_scene_usb_uart_on_exit(void* context) { | void gpio_scene_usb_uart_on_exit(void* context) { | ||||||
|     GpioApp* app = context; |     GpioApp* app = context; | ||||||
|     usb_uart_disable(); |     usb_uart_disable(); | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         app->scene_manager, | ||||||
|  |         GpioSceneUsbUart, | ||||||
|  |         variable_item_list_get_selected_item_index(app->var_item_list)); | ||||||
|     variable_item_list_clean(app->var_item_list); |     variable_item_list_clean(app->var_item_list); | ||||||
|     free(usb_uart); |     free(cfg_set); | ||||||
| } | } | ||||||
							
								
								
									
										212
									
								
								applications/gpio/usb_uart_bridge.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								applications/gpio/usb_uart_bridge.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,212 @@ | |||||||
|  | #include "usb_uart_bridge.h" | ||||||
|  | #include "furi-hal.h" | ||||||
|  | #include <stream_buffer.h> | ||||||
|  | #include <furi-hal-usb-cdc_i.h> | ||||||
|  | #include "usb_cdc.h" | ||||||
|  | 
 | ||||||
|  | #define USB_CDC_PKT_LEN CDC_DATA_SZ | ||||||
|  | #define USB_UART_RX_BUF_SIZE (USB_CDC_PKT_LEN * 5) | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     WorkerEvtReserved = (1 << 0), // Reserved for StreamBuffer internal event
 | ||||||
|  |     WorkerEvtStop = (1 << 1), | ||||||
|  |     WorkerEvtRxDone = (1 << 2), | ||||||
|  | 
 | ||||||
|  |     WorkerEvtTxStop = (1 << 3), | ||||||
|  |     WorkerEvtCdcRx = (1 << 4), | ||||||
|  | } WorkerEvtFlags; | ||||||
|  | 
 | ||||||
|  | #define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone) | ||||||
|  | #define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtCdcRx) | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     UsbUartConfig cfg; | ||||||
|  | 
 | ||||||
|  |     FuriThread* thread; | ||||||
|  |     FuriThread* tx_thread; | ||||||
|  | 
 | ||||||
|  |     StreamBufferHandle_t rx_stream; | ||||||
|  | 
 | ||||||
|  |     osMutexId_t usb_mutex; | ||||||
|  | 
 | ||||||
|  |     osSemaphoreId_t tx_sem; | ||||||
|  | 
 | ||||||
|  |     uint8_t rx_buf[USB_CDC_PKT_LEN]; | ||||||
|  | 
 | ||||||
|  |     bool buf_full; | ||||||
|  | } UsbUartParams; | ||||||
|  | 
 | ||||||
|  | static UsbUartParams* usb_uart; | ||||||
|  | static bool running = false; | ||||||
|  | 
 | ||||||
|  | static void vcp_on_cdc_tx_complete(); | ||||||
|  | static void vcp_on_cdc_rx(); | ||||||
|  | static void vcp_state_callback(uint8_t state); | ||||||
|  | static void vcp_on_cdc_control_line(uint8_t state); | ||||||
|  | static void vcp_on_line_config(struct usb_cdc_line_coding* config); | ||||||
|  | 
 | ||||||
|  | static CdcCallbacks cdc_cb = { | ||||||
|  |     vcp_on_cdc_tx_complete, | ||||||
|  |     vcp_on_cdc_rx, | ||||||
|  |     vcp_state_callback, | ||||||
|  |     vcp_on_cdc_control_line, | ||||||
|  |     vcp_on_line_config, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* USB UART worker */ | ||||||
|  | 
 | ||||||
|  | static int32_t usb_uart_tx_thread(void* context); | ||||||
|  | 
 | ||||||
|  | static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data) { | ||||||
|  |     BaseType_t xHigherPriorityTaskWoken = pdFALSE; | ||||||
|  | 
 | ||||||
|  |     if(ev == UartIrqEventRXNE) { | ||||||
|  |         xStreamBufferSendFromISR(usb_uart->rx_stream, &data, 1, &xHigherPriorityTaskWoken); | ||||||
|  |         portYIELD_FROM_ISR(xHigherPriorityTaskWoken); | ||||||
|  |         osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->thread), WorkerEvtRxDone); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int32_t usb_uart_worker(void* context) { | ||||||
|  |     memcpy(&usb_uart->cfg, context, sizeof(UsbUartConfig)); | ||||||
|  | 
 | ||||||
|  |     usb_uart->rx_stream = xStreamBufferCreate(USB_UART_RX_BUF_SIZE, 1); | ||||||
|  | 
 | ||||||
|  |     usb_uart->tx_sem = osSemaphoreNew(1, 1, NULL); | ||||||
|  |     usb_uart->usb_mutex = osMutexNew(NULL); | ||||||
|  | 
 | ||||||
|  |     usb_uart->tx_thread = furi_thread_alloc(); | ||||||
|  |     furi_thread_set_name(usb_uart->tx_thread, "UsbUartTxWorker"); | ||||||
|  |     furi_thread_set_stack_size(usb_uart->tx_thread, 512); | ||||||
|  |     furi_thread_set_context(usb_uart->tx_thread, NULL); | ||||||
|  |     furi_thread_set_callback(usb_uart->tx_thread, usb_uart_tx_thread); | ||||||
|  | 
 | ||||||
|  |     UsbMode usb_mode_prev = furi_hal_usb_get_config(); | ||||||
|  |     if(usb_uart->cfg.vcp_ch == 0) { | ||||||
|  |         furi_hal_usb_set_config(UsbModeVcpSingle); | ||||||
|  |         furi_hal_vcp_disable(); | ||||||
|  |     } else { | ||||||
|  |         furi_hal_usb_set_config(UsbModeVcpDual); | ||||||
|  |     } | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtCdcRx); | ||||||
|  | 
 | ||||||
|  |     if(usb_uart->cfg.uart_ch == FuriHalUartIdUSART1) { | ||||||
|  |         furi_hal_console_disable(); | ||||||
|  |     } else if(usb_uart->cfg.uart_ch == FuriHalUartIdLPUART1) { | ||||||
|  |         furi_hal_uart_init(usb_uart->cfg.uart_ch, 115200); | ||||||
|  |         furi_hal_uart_set_irq_cb(usb_uart->cfg.uart_ch, usb_uart_on_irq_cb); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_hal_uart_set_irq_cb(usb_uart->cfg.uart_ch, usb_uart_on_irq_cb); | ||||||
|  |     if(usb_uart->cfg.baudrate != 0) | ||||||
|  |         furi_hal_uart_set_br(usb_uart->cfg.uart_ch, usb_uart->cfg.baudrate); | ||||||
|  |     else | ||||||
|  |         vcp_on_line_config(furi_hal_cdc_get_port_settings(usb_uart->cfg.vcp_ch)); | ||||||
|  | 
 | ||||||
|  |     furi_hal_cdc_set_callbacks(usb_uart->cfg.vcp_ch, &cdc_cb); | ||||||
|  | 
 | ||||||
|  |     furi_thread_start(usb_uart->tx_thread); | ||||||
|  | 
 | ||||||
|  |     while(1) { | ||||||
|  |         uint32_t events = osThreadFlagsWait(WORKER_ALL_RX_EVENTS, osFlagsWaitAny, osWaitForever); | ||||||
|  |         furi_check((events & osFlagsError) == 0); | ||||||
|  |         if(events & WorkerEvtStop) break; | ||||||
|  |         if(events & WorkerEvtRxDone) { | ||||||
|  |             size_t len = | ||||||
|  |                 xStreamBufferReceive(usb_uart->rx_stream, usb_uart->rx_buf, USB_CDC_PKT_LEN, 0); | ||||||
|  |             if(len > 0) { | ||||||
|  |                 if(osSemaphoreAcquire(usb_uart->tx_sem, 100) == osOK) { | ||||||
|  |                     furi_check(osMutexAcquire(usb_uart->usb_mutex, osWaitForever) == osOK); | ||||||
|  |                     furi_hal_cdc_send(usb_uart->cfg.vcp_ch, usb_uart->rx_buf, len); | ||||||
|  |                     furi_check(osMutexRelease(usb_uart->usb_mutex) == osOK); | ||||||
|  |                 } else { | ||||||
|  |                     xStreamBufferReset(usb_uart->rx_stream); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtTxStop); | ||||||
|  |     furi_thread_join(usb_uart->tx_thread); | ||||||
|  |     furi_thread_free(usb_uart->tx_thread); | ||||||
|  | 
 | ||||||
|  |     if(usb_uart->cfg.uart_ch == FuriHalUartIdUSART1) | ||||||
|  |         furi_hal_console_enable(); | ||||||
|  |     else if(usb_uart->cfg.uart_ch == FuriHalUartIdLPUART1) | ||||||
|  |         furi_hal_uart_deinit(usb_uart->cfg.uart_ch); | ||||||
|  | 
 | ||||||
|  |     furi_hal_cdc_set_callbacks(usb_uart->cfg.vcp_ch, NULL); | ||||||
|  |     furi_hal_usb_set_config(usb_mode_prev); | ||||||
|  |     if(usb_uart->cfg.vcp_ch == 0) furi_hal_vcp_enable(); | ||||||
|  | 
 | ||||||
|  |     vStreamBufferDelete(usb_uart->rx_stream); | ||||||
|  |     osMutexDelete(usb_uart->usb_mutex); | ||||||
|  |     osSemaphoreDelete(usb_uart->tx_sem); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int32_t usb_uart_tx_thread(void* context) { | ||||||
|  |     uint8_t data[USB_CDC_PKT_LEN]; | ||||||
|  |     while(1) { | ||||||
|  |         uint32_t events = osThreadFlagsWait(WORKER_ALL_TX_EVENTS, osFlagsWaitAny, osWaitForever); | ||||||
|  |         furi_check((events & osFlagsError) == 0); | ||||||
|  |         if(events & WorkerEvtTxStop) break; | ||||||
|  |         if(events & WorkerEvtCdcRx) { | ||||||
|  |             furi_check(osMutexAcquire(usb_uart->usb_mutex, osWaitForever) == osOK); | ||||||
|  |             int32_t size = furi_hal_cdc_receive(usb_uart->cfg.vcp_ch, data, USB_CDC_PKT_LEN); | ||||||
|  |             furi_check(osMutexRelease(usb_uart->usb_mutex) == osOK); | ||||||
|  | 
 | ||||||
|  |             if(size > 0) { | ||||||
|  |                 furi_hal_uart_tx(usb_uart->cfg.uart_ch, data, size); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* VCP callbacks */ | ||||||
|  | 
 | ||||||
|  | static void vcp_on_cdc_tx_complete() { | ||||||
|  |     osSemaphoreRelease(usb_uart->tx_sem); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void vcp_on_cdc_rx() { | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtCdcRx); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void vcp_state_callback(uint8_t state) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void vcp_on_cdc_control_line(uint8_t state) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void vcp_on_line_config(struct usb_cdc_line_coding* config) { | ||||||
|  |     if((usb_uart->cfg.baudrate == 0) && (config->dwDTERate != 0)) | ||||||
|  |         furi_hal_uart_set_br(usb_uart->cfg.uart_ch, config->dwDTERate); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void usb_uart_enable(UsbUartConfig* cfg) { | ||||||
|  |     if(running == false) { | ||||||
|  |         running = true; | ||||||
|  |         usb_uart = furi_alloc(sizeof(UsbUartParams)); | ||||||
|  | 
 | ||||||
|  |         usb_uart->thread = furi_thread_alloc(); | ||||||
|  |         furi_thread_set_name(usb_uart->thread, "UsbUartWorker"); | ||||||
|  |         furi_thread_set_stack_size(usb_uart->thread, 1024); | ||||||
|  |         furi_thread_set_context(usb_uart->thread, cfg); | ||||||
|  |         furi_thread_set_callback(usb_uart->thread, usb_uart_worker); | ||||||
|  | 
 | ||||||
|  |         furi_thread_start(usb_uart->thread); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void usb_uart_disable() { | ||||||
|  |     if(running == true) { | ||||||
|  |         osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->thread), WorkerEvtStop); | ||||||
|  |         furi_thread_join(usb_uart->thread); | ||||||
|  |         furi_thread_free(usb_uart->thread); | ||||||
|  |         free(usb_uart); | ||||||
|  |         running = false; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								applications/gpio/usb_uart_bridge.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								applications/gpio/usb_uart_bridge.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <stdint.h> | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t vcp_ch; | ||||||
|  |     uint8_t uart_ch; | ||||||
|  |     uint32_t baudrate; | ||||||
|  | } UsbUartConfig; | ||||||
|  | 
 | ||||||
|  | void usb_uart_enable(UsbUartConfig* cfg); | ||||||
|  | 
 | ||||||
|  | void usb_uart_disable(); | ||||||
							
								
								
									
										36
									
								
								applications/gui/canvas.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										36
									
								
								applications/gui/canvas.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -6,22 +6,32 @@ | |||||||
| #include <furi-hal.h> | #include <furi-hal.h> | ||||||
| #include <u8g2_glue.h> | #include <u8g2_glue.h> | ||||||
| 
 | 
 | ||||||
|  | const CanvasFontParameters canvas_font_params[FontTotalNumber] = { | ||||||
|  |     [FontPrimary] = {.leading_default = 12, .leading_min = 11, .height = 8, .descender = 2}, | ||||||
|  |     [FontSecondary] = {.leading_default = 11, .leading_min = 9, .height = 7, .descender = 2}, | ||||||
|  |     [FontKeyboard] = {.leading_default = 11, .leading_min = 9, .height = 7, .descender = 2}, | ||||||
|  |     [FontBigNumbers] = {.leading_default = 18, .leading_min = 16, .height = 15, .descender = 0}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| Canvas* canvas_init() { | Canvas* canvas_init() { | ||||||
|     Canvas* canvas = furi_alloc(sizeof(Canvas)); |     Canvas* canvas = furi_alloc(sizeof(Canvas)); | ||||||
| 
 | 
 | ||||||
|     furi_hal_power_insomnia_enter(); |     furi_hal_power_insomnia_enter(); | ||||||
| 
 | 
 | ||||||
|     canvas->orientation = CanvasOrientationHorizontal; |     // Setup u8g2
 | ||||||
|     u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); |     u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); | ||||||
| 
 |     canvas->orientation = CanvasOrientationHorizontal; | ||||||
|     // send init sequence to the display, display is in sleep mode after this
 |     // Initialize display
 | ||||||
|     u8g2_InitDisplay(&canvas->fb); |     u8g2_InitDisplay(&canvas->fb); | ||||||
|     // wake up display
 |     // Wake up display
 | ||||||
|     u8g2_ClearBuffer(&canvas->fb); |  | ||||||
|     u8g2_SetPowerSave(&canvas->fb, 0); |     u8g2_SetPowerSave(&canvas->fb, 0); | ||||||
|     u8g2_SendBuffer(&canvas->fb); | 
 | ||||||
|  |     // Clear buffer and send to device
 | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     canvas_commit(canvas); | ||||||
| 
 | 
 | ||||||
|     furi_hal_power_insomnia_exit(); |     furi_hal_power_insomnia_exit(); | ||||||
|  | 
 | ||||||
|     return canvas; |     return canvas; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -32,9 +42,12 @@ void canvas_free(Canvas* canvas) { | |||||||
| 
 | 
 | ||||||
| void canvas_reset(Canvas* canvas) { | void canvas_reset(Canvas* canvas) { | ||||||
|     furi_assert(canvas); |     furi_assert(canvas); | ||||||
|  | 
 | ||||||
|     canvas_clear(canvas); |     canvas_clear(canvas); | ||||||
|  | 
 | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
|     canvas_set_font(canvas, FontSecondary); |     canvas_set_font(canvas, FontSecondary); | ||||||
|  |     canvas_set_font_direction(canvas, CanvasFontDirectionLeftToRight); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void canvas_commit(Canvas* canvas) { | void canvas_commit(Canvas* canvas) { | ||||||
| @ -86,6 +99,12 @@ uint8_t canvas_current_font_height(Canvas* canvas) { | |||||||
|     return font_height; |     return font_height; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | CanvasFontParameters* canvas_get_font_params(Canvas* canvas, Font font) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  |     furi_assert(font < FontTotalNumber); | ||||||
|  |     return (CanvasFontParameters*)&canvas_font_params[font]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void canvas_clear(Canvas* canvas) { | void canvas_clear(Canvas* canvas) { | ||||||
|     furi_assert(canvas); |     furi_assert(canvas); | ||||||
|     u8g2_ClearBuffer(&canvas->fb); |     u8g2_ClearBuffer(&canvas->fb); | ||||||
| @ -96,6 +115,11 @@ void canvas_set_color(Canvas* canvas, Color color) { | |||||||
|     u8g2_SetDrawColor(&canvas->fb, color); |     u8g2_SetDrawColor(&canvas->fb, color); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void canvas_set_font_direction(Canvas* canvas, CanvasFontDirection dir) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  |     u8g2_SetFontDirection(&canvas->fb, dir); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void canvas_invert_color(Canvas* canvas) { | void canvas_invert_color(Canvas* canvas) { | ||||||
|     canvas->fb.draw_color = !canvas->fb.draw_color; |     canvas->fb.draw_color = !canvas->fb.draw_color; | ||||||
| } | } | ||||||
|  | |||||||
| @ -20,7 +20,15 @@ typedef enum { | |||||||
| } Color; | } Color; | ||||||
| 
 | 
 | ||||||
| /** Fonts enumeration */ | /** Fonts enumeration */ | ||||||
| typedef enum { FontPrimary, FontSecondary, FontKeyboard, FontBigNumbers } Font; | typedef enum { | ||||||
|  |     FontPrimary, | ||||||
|  |     FontSecondary, | ||||||
|  |     FontKeyboard, | ||||||
|  |     FontBigNumbers, | ||||||
|  | 
 | ||||||
|  |     // Keep last for fonts number calculation
 | ||||||
|  |     FontTotalNumber, | ||||||
|  | } Font; | ||||||
| 
 | 
 | ||||||
| /** Alignment enumeration */ | /** Alignment enumeration */ | ||||||
| typedef enum { | typedef enum { | ||||||
| @ -37,6 +45,22 @@ typedef enum { | |||||||
|     CanvasOrientationVertical, |     CanvasOrientationVertical, | ||||||
| } CanvasOrientation; | } CanvasOrientation; | ||||||
| 
 | 
 | ||||||
|  | /** Font Direction */ | ||||||
|  | typedef enum { | ||||||
|  |     CanvasFontDirectionLeftToRight, | ||||||
|  |     CanvasFontDirectionTopToDown, | ||||||
|  |     CanvasFontDirectionRightToLeft, | ||||||
|  |     CanvasFontDirectionDownToTop, | ||||||
|  | } CanvasFontDirection; | ||||||
|  | 
 | ||||||
|  | /** Font parameters */ | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t leading_default; | ||||||
|  |     uint8_t leading_min; | ||||||
|  |     uint8_t height; | ||||||
|  |     uint8_t descender; | ||||||
|  | } CanvasFontParameters; | ||||||
|  | 
 | ||||||
| /** Canvas anonymouse structure */ | /** Canvas anonymouse structure */ | ||||||
| typedef struct Canvas Canvas; | typedef struct Canvas Canvas; | ||||||
| 
 | 
 | ||||||
| @ -64,6 +88,15 @@ uint8_t canvas_height(Canvas* canvas); | |||||||
|  */ |  */ | ||||||
| uint8_t canvas_current_font_height(Canvas* canvas); | uint8_t canvas_current_font_height(Canvas* canvas); | ||||||
| 
 | 
 | ||||||
|  | /** Get font parameters
 | ||||||
|  |  * | ||||||
|  |  * @param      canvas  Canvas instance | ||||||
|  |  * @param      font    Font | ||||||
|  |  * | ||||||
|  |  * @return     pointer to CanvasFontParameters structure | ||||||
|  |  */ | ||||||
|  | CanvasFontParameters* canvas_get_font_params(Canvas* canvas, Font font); | ||||||
|  | 
 | ||||||
| /** Clear canvas
 | /** Clear canvas
 | ||||||
|  * |  * | ||||||
|  * @param      canvas  Canvas instance |  * @param      canvas  Canvas instance | ||||||
| @ -77,6 +110,14 @@ void canvas_clear(Canvas* canvas); | |||||||
|  */ |  */ | ||||||
| void canvas_set_color(Canvas* canvas, Color color); | void canvas_set_color(Canvas* canvas, Color color); | ||||||
| 
 | 
 | ||||||
|  | /** Set font swap
 | ||||||
|  |  * Argument String Rotation Description | ||||||
|  |  * | ||||||
|  |  * @param      canvas  Canvas instance | ||||||
|  |  * @param      dir     Direction font | ||||||
|  |  */ | ||||||
|  | void canvas_set_font_direction(Canvas* canvas, CanvasFontDirection dir); | ||||||
|  | 
 | ||||||
| /** Invert drawing color
 | /** Invert drawing color
 | ||||||
|  * |  * | ||||||
|  * @param      canvas  Canvas instance |  * @param      canvas  Canvas instance | ||||||
|  | |||||||
							
								
								
									
										215
									
								
								applications/gui/elements.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										215
									
								
								applications/gui/elements.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -10,6 +10,18 @@ | |||||||
| 
 | 
 | ||||||
| #include <string.h> | #include <string.h> | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
|  | #include <stdbool.h> | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t x; | ||||||
|  |     uint8_t y; | ||||||
|  |     uint8_t leading_min; | ||||||
|  |     uint8_t leading_default; | ||||||
|  |     uint8_t height; | ||||||
|  |     uint8_t descender; | ||||||
|  |     uint8_t len; | ||||||
|  |     const char* text; | ||||||
|  | } ElementTextBoxLine; | ||||||
| 
 | 
 | ||||||
| void elements_progress_bar( | void elements_progress_bar( | ||||||
|     Canvas* canvas, |     Canvas* canvas, | ||||||
| @ -352,3 +364,206 @@ void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) { | |||||||
|         string_cat(string, "..."); |         string_cat(string, "..."); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void elements_text_box( | ||||||
|  |     Canvas* canvas, | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     uint8_t width, | ||||||
|  |     uint8_t height, | ||||||
|  |     Align horizontal, | ||||||
|  |     Align vertical, | ||||||
|  |     const char* text) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  | 
 | ||||||
|  |     ElementTextBoxLine line[ELEMENTS_MAX_LINES_NUM]; | ||||||
|  |     bool bold = false; | ||||||
|  |     bool mono = false; | ||||||
|  |     bool inversed = false; | ||||||
|  |     bool inversed_present = false; | ||||||
|  |     Font current_font = FontSecondary; | ||||||
|  |     Font prev_font = FontSecondary; | ||||||
|  |     CanvasFontParameters* font_params = canvas_get_font_params(canvas, current_font); | ||||||
|  | 
 | ||||||
|  |     // Fill line parameters
 | ||||||
|  |     uint8_t line_leading_min = font_params->leading_min; | ||||||
|  |     uint8_t line_leading_default = font_params->leading_default; | ||||||
|  |     uint8_t line_height = font_params->height; | ||||||
|  |     uint8_t line_descender = font_params->descender; | ||||||
|  |     uint8_t line_num = 0; | ||||||
|  |     uint8_t line_width = 0; | ||||||
|  |     uint8_t line_len = 0; | ||||||
|  |     uint8_t total_height_min = 0; | ||||||
|  |     uint8_t total_height_default = 0; | ||||||
|  |     uint16_t i = 0; | ||||||
|  |     bool full_text_processed = false; | ||||||
|  | 
 | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  | 
 | ||||||
|  |     // Fill all lines
 | ||||||
|  |     line[0].text = text; | ||||||
|  |     for(i = 0; !full_text_processed; i++) { | ||||||
|  |         line_len++; | ||||||
|  |         // Identify line height
 | ||||||
|  |         if(prev_font != current_font) { | ||||||
|  |             font_params = canvas_get_font_params(canvas, current_font); | ||||||
|  |             line_leading_min = MAX(line_leading_min, font_params->leading_min); | ||||||
|  |             line_leading_default = MAX(line_leading_default, font_params->leading_default); | ||||||
|  |             line_height = MAX(line_height, font_params->height); | ||||||
|  |             line_descender = MAX(line_descender, font_params->descender); | ||||||
|  |             prev_font = current_font; | ||||||
|  |         } | ||||||
|  |         // Set the font
 | ||||||
|  |         if(text[i] == '\e' && text[i + 1]) { | ||||||
|  |             i++; | ||||||
|  |             line_len++; | ||||||
|  |             if(text[i] == ELEMENTS_BOLD_MARKER) { | ||||||
|  |                 if(bold) { | ||||||
|  |                     current_font = FontSecondary; | ||||||
|  |                 } else { | ||||||
|  |                     current_font = FontPrimary; | ||||||
|  |                 } | ||||||
|  |                 canvas_set_font(canvas, current_font); | ||||||
|  |                 bold = !bold; | ||||||
|  |             } | ||||||
|  |             if(text[i] == ELEMENTS_MONO_MARKER) { | ||||||
|  |                 if(mono) { | ||||||
|  |                     current_font = FontSecondary; | ||||||
|  |                 } else { | ||||||
|  |                     current_font = FontKeyboard; | ||||||
|  |                 } | ||||||
|  |                 canvas_set_font(canvas, FontKeyboard); | ||||||
|  |                 mono = !mono; | ||||||
|  |             } | ||||||
|  |             if(text[i] == ELEMENTS_INVERSED_MARKER) { | ||||||
|  |                 inversed_present = true; | ||||||
|  |             } | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         if(text[i] != '\n') { | ||||||
|  |             line_width += canvas_glyph_width(canvas, text[i]); | ||||||
|  |         } | ||||||
|  |         // Process new line
 | ||||||
|  |         if(text[i] == '\n' || text[i] == '\0' || line_width > width) { | ||||||
|  |             if(line_width > width) { | ||||||
|  |                 line_width -= canvas_glyph_width(canvas, text[i--]); | ||||||
|  |                 line_len--; | ||||||
|  |             } | ||||||
|  |             if(text[i] == '\0') { | ||||||
|  |                 full_text_processed = true; | ||||||
|  |             } | ||||||
|  |             if(inversed_present) { | ||||||
|  |                 line_leading_min += 1; | ||||||
|  |                 line_leading_default += 1; | ||||||
|  |                 inversed_present = false; | ||||||
|  |             } | ||||||
|  |             line[line_num].leading_min = line_leading_min; | ||||||
|  |             line[line_num].leading_default = line_leading_default; | ||||||
|  |             line[line_num].height = line_height; | ||||||
|  |             line[line_num].descender = line_descender; | ||||||
|  |             if(total_height_min + line_leading_min > height) { | ||||||
|  |                 line_num--; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             total_height_min += line_leading_min; | ||||||
|  |             total_height_default += line_leading_default; | ||||||
|  |             line[line_num].len = line_len; | ||||||
|  |             if(horizontal == AlignCenter) { | ||||||
|  |                 line[line_num].x = x + (width - line_width) / 2; | ||||||
|  |             } else if(horizontal == AlignRight) { | ||||||
|  |                 line[line_num].x = x + (width - line_width); | ||||||
|  |             } else { | ||||||
|  |                 line[line_num].x = x; | ||||||
|  |             } | ||||||
|  |             line[line_num].y = total_height_min; | ||||||
|  |             line_num++; | ||||||
|  |             if(text[i + 1]) { | ||||||
|  |                 line[line_num].text = &text[i + 1]; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             line_leading_min = font_params->leading_min; | ||||||
|  |             line_height = font_params->height; | ||||||
|  |             line_descender = font_params->descender; | ||||||
|  |             line_width = 0; | ||||||
|  |             line_len = 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Set vertical alignment for all lines
 | ||||||
|  |     if(full_text_processed) { | ||||||
|  |         if(total_height_default < height) { | ||||||
|  |             if(vertical == AlignTop) { | ||||||
|  |                 line[0].y = y + line[0].height; | ||||||
|  |             } else if(vertical == AlignCenter) { | ||||||
|  |                 line[0].y = y + line[0].height + (height - total_height_default) / 2; | ||||||
|  |             } else if(vertical == AlignBottom) { | ||||||
|  |                 line[0].y = y + line[0].height + (height - total_height_default); | ||||||
|  |             } | ||||||
|  |             if(line_num > 1) { | ||||||
|  |                 for(uint8_t i = 1; i < line_num; i++) { | ||||||
|  |                     line[i].y = line[i - 1].y + line[i - 1].leading_default; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } else if(line_num > 1) { | ||||||
|  |             uint8_t free_pixel_num = height - total_height_min; | ||||||
|  |             uint8_t fill_pixel = 0; | ||||||
|  |             uint8_t j = 1; | ||||||
|  |             line[0].y = line[0].height; | ||||||
|  |             while(fill_pixel < free_pixel_num) { | ||||||
|  |                 line[j].y = line[j - 1].y + line[j - 1].leading_min + 1; | ||||||
|  |                 fill_pixel++; | ||||||
|  |                 j = j % (line_num - 1) + 1; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Draw line by line
 | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  |     bold = false; | ||||||
|  |     mono = false; | ||||||
|  |     inversed = false; | ||||||
|  |     for(uint8_t i = 0; i < line_num; i++) { | ||||||
|  |         for(uint8_t j = 0; j < line[i].len; j++) { | ||||||
|  |             // Process format symbols
 | ||||||
|  |             if(line[i].text[j] == ELEMENTS_BOLD_MARKER) { | ||||||
|  |                 if(bold) { | ||||||
|  |                     current_font = FontSecondary; | ||||||
|  |                 } else { | ||||||
|  |                     current_font = FontPrimary; | ||||||
|  |                 } | ||||||
|  |                 canvas_set_font(canvas, current_font); | ||||||
|  |                 bold = !bold; | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             if(line[i].text[j] == ELEMENTS_MONO_MARKER) { | ||||||
|  |                 if(mono) { | ||||||
|  |                     current_font = FontSecondary; | ||||||
|  |                 } else { | ||||||
|  |                     current_font = FontKeyboard; | ||||||
|  |                 } | ||||||
|  |                 canvas_set_font(canvas, current_font); | ||||||
|  |                 mono = !mono; | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             if(line[i].text[j] == ELEMENTS_INVERSED_MARKER) { | ||||||
|  |                 inversed = !inversed; | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             if(inversed) { | ||||||
|  |                 canvas_draw_box( | ||||||
|  |                     canvas, | ||||||
|  |                     line[i].x - 1, | ||||||
|  |                     line[i].y - line[i].height - 1, | ||||||
|  |                     canvas_glyph_width(canvas, line[i].text[j]) + 1, | ||||||
|  |                     line[i].height + line[i].descender + 2); | ||||||
|  |                 canvas_invert_color(canvas); | ||||||
|  |                 canvas_draw_glyph(canvas, line[i].x, line[i].y, line[i].text[j]); | ||||||
|  |                 canvas_invert_color(canvas); | ||||||
|  |             } else { | ||||||
|  |                 canvas_draw_glyph(canvas, line[i].x, line[i].y, line[i].text[j]); | ||||||
|  |             } | ||||||
|  |             line[i].x += canvas_glyph_width(canvas, line[i].text[j]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										127
									
								
								applications/gui/elements.h
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										127
									
								
								applications/gui/elements.h
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -16,12 +16,19 @@ | |||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | #define ELEMENTS_MAX_LINES_NUM (7) | ||||||
|  | #define ELEMENTS_BOLD_MARKER '#' | ||||||
|  | #define ELEMENTS_MONO_MARKER '*' | ||||||
|  | #define ELEMENTS_INVERSED_MARKER '!' | ||||||
|  | 
 | ||||||
| /** Draw progress bar.
 | /** Draw progress bar.
 | ||||||
|  * @param x - progress bar position on X axis |  * | ||||||
|  * @param y - progress bar position on Y axis |  * @param   canvas      Canvas instance | ||||||
|  * @param width - progress bar width |  * @param   x           progress bar position on X axis | ||||||
|  * @param progress - progress in unnamed metric |  * @param   y           progress bar position on Y axis | ||||||
|  * @param total - total amount in unnamed metric |  * @param   width       progress bar width | ||||||
|  |  * @param   progress    progress in unnamed metric | ||||||
|  |  * @param   total       total amount in unnamed metric | ||||||
|  */ |  */ | ||||||
| void elements_progress_bar( | void elements_progress_bar( | ||||||
|     Canvas* canvas, |     Canvas* canvas, | ||||||
| @ -32,11 +39,13 @@ void elements_progress_bar( | |||||||
|     uint8_t total); |     uint8_t total); | ||||||
| 
 | 
 | ||||||
| /** Draw scrollbar on canvas at specific position.
 | /** Draw scrollbar on canvas at specific position.
 | ||||||
|  * @param x - scrollbar position on X axis |  * | ||||||
|  * @param y - scrollbar position on Y axis |  * @param   canvas  Canvas instance | ||||||
|  * @param height - scrollbar height |  * @param   x       scrollbar position on X axis | ||||||
|  * @param pos - current element  |  * @param   y       scrollbar position on Y axis | ||||||
|  * @param total - total elements |  * @param   height  scrollbar height | ||||||
|  |  * @param   pos     current element | ||||||
|  |  * @param   total   total elements | ||||||
|  */ |  */ | ||||||
| void elements_scrollbar_pos( | void elements_scrollbar_pos( | ||||||
|     Canvas* canvas, |     Canvas* canvas, | ||||||
| @ -47,37 +56,49 @@ void elements_scrollbar_pos( | |||||||
|     uint16_t total); |     uint16_t total); | ||||||
| 
 | 
 | ||||||
| /** Draw scrollbar on canvas.
 | /** Draw scrollbar on canvas.
 | ||||||
|  * width 3px, height equal to canvas height |  * @note    width 3px, height equal to canvas height | ||||||
|  * @param pos - current element of total elements |  * | ||||||
|  * @param total - total elements |  * @param   canvas  Canvas instance | ||||||
|  |  * @param   pos     current element of total elements | ||||||
|  |  * @param   total   total elements | ||||||
|  */ |  */ | ||||||
| void elements_scrollbar(Canvas* canvas, uint16_t pos, uint16_t total); | void elements_scrollbar(Canvas* canvas, uint16_t pos, uint16_t total); | ||||||
| 
 | 
 | ||||||
| /** Draw rounded frame
 | /** Draw rounded frame
 | ||||||
|  * @param x, y - top left corner coordinates |  * | ||||||
|  * @param width, height - frame width and height |  * @param   canvas          Canvas instance | ||||||
|  |  * @param   x, y            top left corner coordinates | ||||||
|  |  * @param   width, height   frame width and height | ||||||
|  */ |  */ | ||||||
| void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); | void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); | ||||||
| 
 | 
 | ||||||
| /** Draw button in left corner
 | /** Draw button in left corner
 | ||||||
|  * @param str - button text |  * | ||||||
|  |  * @param   canvas  Canvas instance | ||||||
|  |  * @param   str     button text | ||||||
|  */ |  */ | ||||||
| void elements_button_left(Canvas* canvas, const char* str); | void elements_button_left(Canvas* canvas, const char* str); | ||||||
| 
 | 
 | ||||||
| /** Draw button in right corner
 | /** Draw button in right corner
 | ||||||
|  * @param str - button text |  * | ||||||
|  |  * @param   canvas  Canvas instance | ||||||
|  |  * @param   str     button text | ||||||
|  */ |  */ | ||||||
| void elements_button_right(Canvas* canvas, const char* str); | void elements_button_right(Canvas* canvas, const char* str); | ||||||
| 
 | 
 | ||||||
| /** Draw button in center
 | /** Draw button in center
 | ||||||
|  * @param str - button text |  * | ||||||
|  |  * @param   canvas  Canvas instance | ||||||
|  |  * @param   str     button text | ||||||
|  */ |  */ | ||||||
| void elements_button_center(Canvas* canvas, const char* str); | void elements_button_center(Canvas* canvas, const char* str); | ||||||
| 
 | 
 | ||||||
| /** Draw aligned multiline text
 | /** Draw aligned multiline text
 | ||||||
|  * @param x, y - coordinates based on align param |  * | ||||||
|  * @param horizontal, vertical - aligment of multiline text |  * @param   canvas                  Canvas instance | ||||||
|  * @param text - string (possible multiline) |  * @param   x, y                    coordinates based on align param | ||||||
|  |  * @param   horizontal, vertical    aligment of multiline text | ||||||
|  |  * @param   text                    string (possible multiline) | ||||||
|  */ |  */ | ||||||
| void elements_multiline_text_aligned( | void elements_multiline_text_aligned( | ||||||
|     Canvas* canvas, |     Canvas* canvas, | ||||||
| @ -88,20 +109,26 @@ void elements_multiline_text_aligned( | |||||||
|     const char* text); |     const char* text); | ||||||
| 
 | 
 | ||||||
| /** Draw multiline text
 | /** Draw multiline text
 | ||||||
|  * @param x, y - top left corner coordinates |  * | ||||||
|  * @param text - string (possible multiline) |  * @param   canvas  Canvas instance | ||||||
|  |  * @param   x, y    top left corner coordinates | ||||||
|  |  * @param   text    string (possible multiline) | ||||||
|  */ |  */ | ||||||
| void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, const char* text); | void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, const char* text); | ||||||
| 
 | 
 | ||||||
| /** Draw framed multiline text
 | /** Draw framed multiline text
 | ||||||
|  * @param x, y - top left corner coordinates |  * | ||||||
|  * @param text - string (possible multiline) |  * @param   canvas  Canvas instance | ||||||
|  |  * @param   x, y    top left corner coordinates | ||||||
|  |  * @param   text    string (possible multiline) | ||||||
|  */ |  */ | ||||||
| void elements_multiline_text_framed(Canvas* canvas, uint8_t x, uint8_t y, const char* text); | void elements_multiline_text_framed(Canvas* canvas, uint8_t x, uint8_t y, const char* text); | ||||||
| 
 | 
 | ||||||
| /** Draw slightly rounded frame
 | /** Draw slightly rounded frame
 | ||||||
|  * @param x, y - top left corner coordinates |  * | ||||||
|  * @param width, height - size of frame |  * @param   canvas          Canvas instance | ||||||
|  |  * @param   x, y            top left corner coordinates | ||||||
|  |  * @param   width, height   size of frame | ||||||
|  */ |  */ | ||||||
| void elements_slightly_rounded_frame( | void elements_slightly_rounded_frame( | ||||||
|     Canvas* canvas, |     Canvas* canvas, | ||||||
| @ -111,8 +138,10 @@ void elements_slightly_rounded_frame( | |||||||
|     uint8_t height); |     uint8_t height); | ||||||
| 
 | 
 | ||||||
| /** Draw slightly rounded box
 | /** Draw slightly rounded box
 | ||||||
|  * @param x, y - top left corner coordinates |  * | ||||||
|  * @param width, height - size of box |  * @param   canvas          Canvas instance | ||||||
|  |  * @param   x, y            top left corner coordinates | ||||||
|  |  * @param   width, height   size of box | ||||||
|  */ |  */ | ||||||
| void elements_slightly_rounded_box( | void elements_slightly_rounded_box( | ||||||
|     Canvas* canvas, |     Canvas* canvas, | ||||||
| @ -122,19 +151,47 @@ void elements_slightly_rounded_box( | |||||||
|     uint8_t height); |     uint8_t height); | ||||||
| 
 | 
 | ||||||
| /** Draw bubble frame for text
 | /** Draw bubble frame for text
 | ||||||
|  * @param x - left x coordinates |  * | ||||||
|  * @param y - top y coordinate |  * @param   canvas  Canvas instance | ||||||
|  * @param width - bubble width |  * @param   x       left x coordinates | ||||||
|  * @param height - bubble height |  * @param   y       top y coordinate | ||||||
|  |  * @param   width   bubble width | ||||||
|  |  * @param   height  bubble height | ||||||
|  */ |  */ | ||||||
| void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); | void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); | ||||||
| 
 | 
 | ||||||
| /** Trim string buffer to fit width in pixels
 | /** Trim string buffer to fit width in pixels
 | ||||||
|  * @param string - string to trim |  * | ||||||
|  * @param width - max width |  * @param   canvas  Canvas instance | ||||||
|  |  * @param   string  string to trim | ||||||
|  |  * @param   width   max width | ||||||
|  */ |  */ | ||||||
| void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width); | void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width); | ||||||
| 
 | 
 | ||||||
|  | /** Draw text box element
 | ||||||
|  |  * | ||||||
|  |  * @param       canvas      Canvas instance | ||||||
|  |  * @param       x           x coordinate | ||||||
|  |  * @param       y           y coordinate | ||||||
|  |  * @param       width       width to fit text | ||||||
|  |  * @param       height      height to fit text | ||||||
|  |  * @param       horizontal  Align instance | ||||||
|  |  * @param       vertical    Align instance | ||||||
|  |  * @param[in]   text        Formatted text. The following formats are available: | ||||||
|  |  *                          "\e#Bold text\e#" - bold font is used | ||||||
|  |  *                          "\e*Monospaced text\e*" - monospaced font is used | ||||||
|  |  *                          "\e#Inversed text\e#" - white text on black background | ||||||
|  |  */ | ||||||
|  | void elements_text_box( | ||||||
|  |     Canvas* canvas, | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     uint8_t width, | ||||||
|  |     uint8_t height, | ||||||
|  |     Align horizontal, | ||||||
|  |     Align vertical, | ||||||
|  |     const char* text); | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
| #include "gui_i.h" | #include "gui_i.h" | ||||||
| 
 | 
 | ||||||
|  | #define TAG "GuiSrv" | ||||||
|  | 
 | ||||||
| ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) { | ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) { | ||||||
|     // Iterating backward
 |     // Iterating backward
 | ||||||
|     ViewPortArray_it_t it; |     ViewPortArray_it_t it; | ||||||
| @ -189,8 +191,8 @@ void gui_input(Gui* gui, InputEvent* input_event) { | |||||||
|     } else if(input_event->type == InputTypePress) { |     } else if(input_event->type == InputTypePress) { | ||||||
|         gui->ongoing_input |= key_bit; |         gui->ongoing_input |= key_bit; | ||||||
|     } else if(!(gui->ongoing_input & key_bit)) { |     } else if(!(gui->ongoing_input & key_bit)) { | ||||||
|         FURI_LOG_W( |         FURI_LOG_D( | ||||||
|             "Gui", |             TAG, | ||||||
|             "non-complementary input, discarding key: %s type: %s, sequence: %p", |             "non-complementary input, discarding key: %s type: %s, sequence: %p", | ||||||
|             input_get_key_name(input_event->key), |             input_get_key_name(input_event->key), | ||||||
|             input_get_type_name(input_event->type), |             input_get_type_name(input_event->type), | ||||||
| @ -211,8 +213,8 @@ void gui_input(Gui* gui, InputEvent* input_event) { | |||||||
|     if(view_port && view_port == gui->ongoing_input_view_port) { |     if(view_port && view_port == gui->ongoing_input_view_port) { | ||||||
|         view_port_input(view_port, input_event); |         view_port_input(view_port, input_event); | ||||||
|     } else if(gui->ongoing_input_view_port && input_event->type == InputTypeRelease) { |     } else if(gui->ongoing_input_view_port && input_event->type == InputTypeRelease) { | ||||||
|         FURI_LOG_W( |         FURI_LOG_D( | ||||||
|             "Gui", |             TAG, | ||||||
|             "ViewPort changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view port", |             "ViewPort changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view port", | ||||||
|             gui->ongoing_input_view_port, |             gui->ongoing_input_view_port, | ||||||
|             view_port, |             view_port, | ||||||
| @ -221,8 +223,8 @@ void gui_input(Gui* gui, InputEvent* input_event) { | |||||||
|             input_event->sequence); |             input_event->sequence); | ||||||
|         view_port_input(gui->ongoing_input_view_port, input_event); |         view_port_input(gui->ongoing_input_view_port, input_event); | ||||||
|     } else { |     } else { | ||||||
|         FURI_LOG_W( |         FURI_LOG_D( | ||||||
|             "Gui", |             TAG, | ||||||
|             "ViewPort changed while key press %p -> %p. Discarding key: %s, type: %s, sequence: %p", |             "ViewPort changed while key press %p -> %p. Discarding key: %s, type: %s, sequence: %p", | ||||||
|             gui->ongoing_input_view_port, |             gui->ongoing_input_view_port, | ||||||
|             view_port, |             view_port, | ||||||
| @ -258,8 +260,7 @@ void gui_cli_screen_stream_callback(uint8_t* data, size_t size, void* context) { | |||||||
| void gui_cli_screen_stream(Cli* cli, string_t args, void* context) { | void gui_cli_screen_stream(Cli* cli, string_t args, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     Gui* gui = context; |     Gui* gui = context; | ||||||
|     gui_set_framebuffer_callback_context(gui, gui); |     gui_set_framebuffer_callback(gui, gui_cli_screen_stream_callback, gui); | ||||||
|     gui_set_framebuffer_callback(gui, gui_cli_screen_stream_callback); |  | ||||||
|     gui_redraw(gui); |     gui_redraw(gui); | ||||||
| 
 | 
 | ||||||
|     // Wait for control events
 |     // Wait for control events
 | ||||||
| @ -279,8 +280,7 @@ void gui_cli_screen_stream(Cli* cli, string_t args, void* context) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     gui_set_framebuffer_callback(gui, NULL); |     gui_set_framebuffer_callback(gui, NULL, NULL); | ||||||
|     gui_set_framebuffer_callback_context(gui, NULL); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer) { | void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer) { | ||||||
| @ -387,14 +387,12 @@ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port) { | |||||||
|     gui_unlock(gui); |     gui_unlock(gui); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback) { | void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) { | ||||||
|     furi_assert(gui); |     furi_assert(gui); | ||||||
|  |     gui_lock(gui); | ||||||
|     gui->canvas_callback = callback; |     gui->canvas_callback = callback; | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void gui_set_framebuffer_callback_context(Gui* gui, void* context) { |  | ||||||
|     furi_assert(gui); |  | ||||||
|     gui->canvas_callback_context = context; |     gui->canvas_callback_context = context; | ||||||
|  |     gui_unlock(gui); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Gui* gui_alloc() { | Gui* gui_alloc() { | ||||||
| @ -414,7 +412,7 @@ Gui* gui_alloc() { | |||||||
|     gui->input_queue = osMessageQueueNew(8, sizeof(InputEvent), NULL); |     gui->input_queue = osMessageQueueNew(8, sizeof(InputEvent), NULL); | ||||||
|     gui->input_events = furi_record_open("input_events"); |     gui->input_events = furi_record_open("input_events"); | ||||||
|     furi_check(gui->input_events); |     furi_check(gui->input_events); | ||||||
|     subscribe_pubsub(gui->input_events, gui_input_events_callback, gui); |     furi_pubsub_subscribe(gui->input_events, gui_input_events_callback, gui); | ||||||
|     // Cli
 |     // Cli
 | ||||||
|     gui->cli = furi_record_open("cli"); |     gui->cli = furi_record_open("cli"); | ||||||
|     cli_add_command( |     cli_add_command( | ||||||
|  | |||||||
| @ -73,15 +73,9 @@ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port); | |||||||
|  * |  * | ||||||
|  * @param      gui       Gui instance |  * @param      gui       Gui instance | ||||||
|  * @param      callback  GuiCanvasCommitCallback |  * @param      callback  GuiCanvasCommitCallback | ||||||
|  |  * @param      context   GuiCanvasCommitCallback context | ||||||
|  */ |  */ | ||||||
| void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback); | void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context); | ||||||
| 
 |  | ||||||
| /** Set gui canvas commit callback context
 |  | ||||||
|  * |  | ||||||
|  * @param      gui      Gui instance |  | ||||||
|  * @param      context  pointer to context |  | ||||||
|  */ |  | ||||||
| void gui_set_framebuffer_callback_context(Gui* gui, void* context); |  | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
|  | |||||||
| @ -50,7 +50,7 @@ struct Gui { | |||||||
| 
 | 
 | ||||||
|     // Input
 |     // Input
 | ||||||
|     osMessageQueueId_t input_queue; |     osMessageQueueId_t input_queue; | ||||||
|     PubSub* input_events; |     FuriPubSub* input_events; | ||||||
|     uint8_t ongoing_input; |     uint8_t ongoing_input; | ||||||
|     ViewPort* ongoing_input_view_port; |     ViewPort* ongoing_input_view_port; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ | |||||||
| #include "icon_i.h" | #include "icon_i.h" | ||||||
| 
 | 
 | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <timers.h> |  | ||||||
| 
 | 
 | ||||||
| IconAnimation* icon_animation_alloc(const Icon* icon) { | IconAnimation* icon_animation_alloc(const Icon* icon) { | ||||||
|     furi_assert(icon); |     furi_assert(icon); | ||||||
|  | |||||||
| @ -246,7 +246,7 @@ void button_menu_clean(ButtonMenu* button_menu) { | |||||||
| 
 | 
 | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         button_menu->view, (ButtonMenuModel * model) { |         button_menu->view, (ButtonMenuModel * model) { | ||||||
|             ButtonMenuItemArray_clean(model->items); |             ButtonMenuItemArray_reset(model->items); | ||||||
|             model->position = 0; |             model->position = 0; | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
|  | |||||||
| @ -139,8 +139,8 @@ void button_panel_clean(ButtonPanel* button_panel) { | |||||||
|             } |             } | ||||||
|             model->reserve_x = 0; |             model->reserve_x = 0; | ||||||
|             model->reserve_y = 0; |             model->reserve_y = 0; | ||||||
|             LabelList_clean(model->labels); |             LabelList_reset(model->labels); | ||||||
|             ButtonMatrix_clean(model->button_matrix); |             ButtonMatrix_reset(model->button_matrix); | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
| @ -150,8 +150,8 @@ static ButtonItem** button_panel_get_item(ButtonPanelModel* model, size_t x, siz | |||||||
| 
 | 
 | ||||||
|     furi_check(x < model->reserve_x); |     furi_check(x < model->reserve_x); | ||||||
|     furi_check(y < model->reserve_y); |     furi_check(y < model->reserve_y); | ||||||
|     ButtonArray_t* button_array = ButtonMatrix_get_at(model->button_matrix, x); |     ButtonArray_t* button_array = ButtonMatrix_safe_get(model->button_matrix, x); | ||||||
|     ButtonItem** button_item = ButtonArray_get_at(*button_array, y); |     ButtonItem** button_item = ButtonArray_safe_get(*button_array, y); | ||||||
|     return button_item; |     return button_item; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										475
									
								
								applications/gui/modules/code_input.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										475
									
								
								applications/gui/modules/code_input.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,475 @@ | |||||||
|  | #include "code_input.h" | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | #define MAX_CODE_LEN 10 | ||||||
|  | 
 | ||||||
|  | struct CodeInput { | ||||||
|  |     View* view; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     CodeInputStateVerify, | ||||||
|  |     CodeInputStateUpdate, | ||||||
|  |     CodeInputStateTotal, | ||||||
|  | } CodeInputStateEnum; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     CodeInputFirst, | ||||||
|  |     CodeInputSecond, | ||||||
|  |     CodeInputTotal, | ||||||
|  | } CodeInputsEnum; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t state; | ||||||
|  |     uint8_t current; | ||||||
|  |     bool ext_update; | ||||||
|  | 
 | ||||||
|  |     uint8_t input_length[CodeInputTotal]; | ||||||
|  |     uint8_t local_buffer[CodeInputTotal][MAX_CODE_LEN]; | ||||||
|  | 
 | ||||||
|  |     CodeInputOkCallback ok_callback; | ||||||
|  |     CodeInputFailCallback fail_callback; | ||||||
|  |     void* callback_context; | ||||||
|  | 
 | ||||||
|  |     const char* header; | ||||||
|  | 
 | ||||||
|  |     uint8_t* ext_buffer; | ||||||
|  |     uint8_t* ext_buffer_length; | ||||||
|  | } CodeInputModel; | ||||||
|  | 
 | ||||||
|  | static const Icon* keys_assets[] = { | ||||||
|  |     [InputKeyUp] = &I_ButtonUp_7x4, | ||||||
|  |     [InputKeyDown] = &I_ButtonDown_7x4, | ||||||
|  |     [InputKeyRight] = &I_ButtonRight_4x7, | ||||||
|  |     [InputKeyLeft] = &I_ButtonLeft_4x7, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Compare buffers | ||||||
|  |  *  | ||||||
|  |  * @param in Input buffer pointer | ||||||
|  |  * @param len_in Input array length | ||||||
|  |  * @param src Source buffer pointer | ||||||
|  |  * @param len_src Source array length | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | bool code_input_compare(uint8_t* in, size_t len_in, uint8_t* src, size_t len_src) { | ||||||
|  |     bool result = false; | ||||||
|  |     do { | ||||||
|  |         result = (len_in && len_src); | ||||||
|  |         if(!result) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         result = (len_in == len_src); | ||||||
|  |         if(!result) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         for(size_t i = 0; i < len_in; i++) { | ||||||
|  |             result = (in[i] == src[i]); | ||||||
|  |             if(!result) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Compare local buffers | ||||||
|  |  *  | ||||||
|  |  * @param model  | ||||||
|  |  */ | ||||||
|  | static bool code_input_compare_local(CodeInputModel* model) { | ||||||
|  |     uint8_t* source = model->local_buffer[CodeInputFirst]; | ||||||
|  |     size_t source_length = model->input_length[CodeInputFirst]; | ||||||
|  | 
 | ||||||
|  |     uint8_t* input = model->local_buffer[CodeInputSecond]; | ||||||
|  |     size_t input_length = model->input_length[CodeInputSecond]; | ||||||
|  | 
 | ||||||
|  |     return code_input_compare(input, input_length, source, source_length); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Compare ext with local | ||||||
|  |  *  | ||||||
|  |  * @param model  | ||||||
|  |  */ | ||||||
|  | static bool code_input_compare_ext(CodeInputModel* model) { | ||||||
|  |     uint8_t* input = model->local_buffer[CodeInputFirst]; | ||||||
|  |     size_t input_length = model->input_length[CodeInputFirst]; | ||||||
|  | 
 | ||||||
|  |     uint8_t* source = model->ext_buffer; | ||||||
|  |     size_t source_length = *model->ext_buffer_length; | ||||||
|  | 
 | ||||||
|  |     return code_input_compare(input, input_length, source, source_length); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Set ext buffer | ||||||
|  |  *  | ||||||
|  |  * @param model  | ||||||
|  |  */ | ||||||
|  | static void code_input_set_ext(CodeInputModel* model) { | ||||||
|  |     *model->ext_buffer_length = model->input_length[CodeInputFirst]; | ||||||
|  |     for(size_t i = 0; i <= model->input_length[CodeInputFirst]; i++) { | ||||||
|  |         model->ext_buffer[i] = model->local_buffer[CodeInputFirst][i]; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Draw input sequence | ||||||
|  |  *  | ||||||
|  |  * @param canvas  | ||||||
|  |  * @param buffer  | ||||||
|  |  * @param length  | ||||||
|  |  * @param x  | ||||||
|  |  * @param y  | ||||||
|  |  * @param active | ||||||
|  |  */ | ||||||
|  | static void code_input_draw_sequence( | ||||||
|  |     Canvas* canvas, | ||||||
|  |     uint8_t* buffer, | ||||||
|  |     uint8_t length, | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     bool active) { | ||||||
|  |     uint8_t pos_x = x + 6; | ||||||
|  |     uint8_t pos_y = y + 3; | ||||||
|  | 
 | ||||||
|  |     if(active) canvas_draw_icon(canvas, x - 4, y + 5, &I_ButtonRightSmall_3x5); | ||||||
|  | 
 | ||||||
|  |     elements_slightly_rounded_frame(canvas, x, y, 116, 15); | ||||||
|  | 
 | ||||||
|  |     for(size_t i = 0; i < length; i++) { | ||||||
|  |         // maybe symmetrical assets? :-/
 | ||||||
|  |         uint8_t offset_y = buffer[i] < 2 ? 2 + (buffer[i] * 2) : 1; | ||||||
|  |         canvas_draw_icon(canvas, pos_x, pos_y + offset_y, keys_assets[buffer[i]]); | ||||||
|  |         pos_x += buffer[i] > 1 ? 9 : 11; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Reset input count | ||||||
|  |  *  | ||||||
|  |  * @param model  | ||||||
|  |  */ | ||||||
|  | static void code_input_reset_count(CodeInputModel* model) { | ||||||
|  |     model->input_length[model->current] = 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Call input callback | ||||||
|  |  *  | ||||||
|  |  * @param model  | ||||||
|  |  */ | ||||||
|  | static void code_input_call_ok_callback(CodeInputModel* model) { | ||||||
|  |     if(model->ok_callback != NULL) { | ||||||
|  |         model->ok_callback(model->callback_context); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Call changed callback | ||||||
|  |  *  | ||||||
|  |  * @param model  | ||||||
|  |  */ | ||||||
|  | static void code_input_call_fail_callback(CodeInputModel* model) { | ||||||
|  |     if(model->fail_callback != NULL) { | ||||||
|  |         model->fail_callback(model->callback_context); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Handle Back button | ||||||
|  |  *  | ||||||
|  |  * @param model  | ||||||
|  |  */ | ||||||
|  | static bool code_input_handle_back(CodeInputModel* model) { | ||||||
|  |     if(model->current && !model->input_length[model->current]) { | ||||||
|  |         --model->current; | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(model->input_length[model->current]) { | ||||||
|  |         code_input_reset_count(model); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     code_input_call_fail_callback(model); | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Handle OK button | ||||||
|  |  *  | ||||||
|  |  * @param model  | ||||||
|  |  */ | ||||||
|  | static void code_input_handle_ok(CodeInputModel* model) { | ||||||
|  |     switch(model->state) { | ||||||
|  |     case CodeInputStateVerify: | ||||||
|  | 
 | ||||||
|  |         if(code_input_compare_ext(model)) { | ||||||
|  |             if(model->ext_update) { | ||||||
|  |                 model->state = CodeInputStateUpdate; | ||||||
|  |             } else { | ||||||
|  |                 code_input_call_ok_callback(model); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         code_input_reset_count(model); | ||||||
|  |         break; | ||||||
|  | 
 | ||||||
|  |     case CodeInputStateUpdate: | ||||||
|  | 
 | ||||||
|  |         if(!model->current && model->input_length[model->current]) { | ||||||
|  |             model->current++; | ||||||
|  |         } else { | ||||||
|  |             if(code_input_compare_local(model)) { | ||||||
|  |                 if(model->ext_update) { | ||||||
|  |                     code_input_set_ext(model); | ||||||
|  |                 } | ||||||
|  |                 code_input_call_ok_callback(model); | ||||||
|  |             } else { | ||||||
|  |                 code_input_reset_count(model); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Handle input | ||||||
|  |  *  | ||||||
|  |  * @param model  | ||||||
|  |  * @param key  | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | size_t code_input_push(uint8_t* buffer, size_t length, InputKey key) { | ||||||
|  |     buffer[length] = key; | ||||||
|  |     length = CLAMP(length + 1, MAX_CODE_LEN, 0); | ||||||
|  |     return length; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Handle D-pad keys | ||||||
|  |  *  | ||||||
|  |  * @param model  | ||||||
|  |  * @param key  | ||||||
|  |  */ | ||||||
|  | static void code_input_handle_dpad(CodeInputModel* model, InputKey key) { | ||||||
|  |     uint8_t at = model->current; | ||||||
|  |     size_t new_length = code_input_push(model->local_buffer[at], model->input_length[at], key); | ||||||
|  |     model->input_length[at] = new_length; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Draw callback | ||||||
|  |  *  | ||||||
|  |  * @param canvas  | ||||||
|  |  * @param _model  | ||||||
|  |  */ | ||||||
|  | static void code_input_view_draw_callback(Canvas* canvas, void* _model) { | ||||||
|  |     CodeInputModel* model = _model; | ||||||
|  |     uint8_t y_offset = 0; | ||||||
|  |     if(!strlen(model->header)) y_offset = 5; | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  | 
 | ||||||
|  |     canvas_draw_str(canvas, 2, 9, model->header); | ||||||
|  | 
 | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  | 
 | ||||||
|  |     switch(model->state) { | ||||||
|  |     case CodeInputStateVerify: | ||||||
|  |         code_input_draw_sequence( | ||||||
|  |             canvas, | ||||||
|  |             model->local_buffer[CodeInputFirst], | ||||||
|  |             model->input_length[CodeInputFirst], | ||||||
|  |             6, | ||||||
|  |             30 - y_offset, | ||||||
|  |             true); | ||||||
|  |         break; | ||||||
|  |     case CodeInputStateUpdate: | ||||||
|  |         code_input_draw_sequence( | ||||||
|  |             canvas, | ||||||
|  |             model->local_buffer[CodeInputFirst], | ||||||
|  |             model->input_length[CodeInputFirst], | ||||||
|  |             6, | ||||||
|  |             14 - y_offset, | ||||||
|  |             !model->current); | ||||||
|  |         code_input_draw_sequence( | ||||||
|  |             canvas, | ||||||
|  |             model->local_buffer[CodeInputSecond], | ||||||
|  |             model->input_length[CodeInputSecond], | ||||||
|  |             6, | ||||||
|  |             44 - y_offset, | ||||||
|  |             model->current); | ||||||
|  | 
 | ||||||
|  |         if(model->current) canvas_draw_str(canvas, 2, 39 - y_offset, "Repeat code"); | ||||||
|  | 
 | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Input callback | ||||||
|  |  *  | ||||||
|  |  * @param event  | ||||||
|  |  * @param context  | ||||||
|  |  * @return true  | ||||||
|  |  * @return false  | ||||||
|  |  */ | ||||||
|  | static bool code_input_view_input_callback(InputEvent* event, void* context) { | ||||||
|  |     CodeInput* code_input = context; | ||||||
|  |     furi_assert(code_input); | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event->type == InputTypeShort || event->type == InputTypeRepeat) { | ||||||
|  |         switch(event->key) { | ||||||
|  |         case InputKeyBack: | ||||||
|  |             with_view_model( | ||||||
|  |                 code_input->view, (CodeInputModel * model) { | ||||||
|  |                     consumed = code_input_handle_back(model); | ||||||
|  |                     return true; | ||||||
|  |                 }); | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case InputKeyOk: | ||||||
|  |             with_view_model( | ||||||
|  |                 code_input->view, (CodeInputModel * model) { | ||||||
|  |                     code_input_handle_ok(model); | ||||||
|  |                     return true; | ||||||
|  |                 }); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         default: | ||||||
|  | 
 | ||||||
|  |             with_view_model( | ||||||
|  |                 code_input->view, (CodeInputModel * model) { | ||||||
|  |                     code_input_handle_dpad(model, event->key); | ||||||
|  |                     return true; | ||||||
|  |                 }); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Reset all input-related data in model | ||||||
|  |  *  | ||||||
|  |  * @param model CodeInputModel | ||||||
|  |  */ | ||||||
|  | static void code_input_reset_model_input_data(CodeInputModel* model) { | ||||||
|  |     model->current = 0; | ||||||
|  |     model->input_length[CodeInputFirst] = 0; | ||||||
|  |     model->input_length[CodeInputSecond] = 0; | ||||||
|  |     model->ext_buffer = NULL; | ||||||
|  |     model->ext_update = false; | ||||||
|  |     model->state = 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 
 | ||||||
|  |  * @brief Allocate and initialize code input. This code input is used to enter codes. | ||||||
|  |  *  | ||||||
|  |  * @return CodeInput instance pointer | ||||||
|  |  */ | ||||||
|  | CodeInput* code_input_alloc() { | ||||||
|  |     CodeInput* code_input = furi_alloc(sizeof(CodeInput)); | ||||||
|  |     code_input->view = view_alloc(); | ||||||
|  |     view_set_context(code_input->view, code_input); | ||||||
|  |     view_allocate_model(code_input->view, ViewModelTypeLocking, sizeof(CodeInputModel)); | ||||||
|  |     view_set_draw_callback(code_input->view, code_input_view_draw_callback); | ||||||
|  |     view_set_input_callback(code_input->view, code_input_view_input_callback); | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         code_input->view, (CodeInputModel * model) { | ||||||
|  |             model->header = ""; | ||||||
|  |             model->ok_callback = NULL; | ||||||
|  |             model->fail_callback = NULL; | ||||||
|  |             model->callback_context = NULL; | ||||||
|  |             code_input_reset_model_input_data(model); | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     return code_input; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 
 | ||||||
|  |  * @brief Deinitialize and free code input | ||||||
|  |  *  | ||||||
|  |  * @param code_input Code input instance | ||||||
|  |  */ | ||||||
|  | void code_input_free(CodeInput* code_input) { | ||||||
|  |     furi_assert(code_input); | ||||||
|  |     view_free(code_input->view); | ||||||
|  |     free(code_input); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 
 | ||||||
|  |  * @brief Get code input view | ||||||
|  |  *  | ||||||
|  |  * @param code_input code input instance | ||||||
|  |  * @return View instance that can be used for embedding | ||||||
|  |  */ | ||||||
|  | View* code_input_get_view(CodeInput* code_input) { | ||||||
|  |     furi_assert(code_input); | ||||||
|  |     return code_input->view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 
 | ||||||
|  |  * @brief Set code input callbacks | ||||||
|  |  *  | ||||||
|  |  * @param code_input code input instance | ||||||
|  |  * @param ok_callback input callback fn | ||||||
|  |  * @param fail_callback code match callback fn | ||||||
|  |  * @param callback_context callback context | ||||||
|  |  * @param buffer buffer  | ||||||
|  |  * @param buffer_length ptr to buffer length uint | ||||||
|  |  * @param ext_update  true to update buffer  | ||||||
|  |  */ | ||||||
|  | void code_input_set_result_callback( | ||||||
|  |     CodeInput* code_input, | ||||||
|  |     CodeInputOkCallback ok_callback, | ||||||
|  |     CodeInputFailCallback fail_callback, | ||||||
|  |     void* callback_context, | ||||||
|  |     uint8_t* buffer, | ||||||
|  |     uint8_t* buffer_length, | ||||||
|  |     bool ext_update) { | ||||||
|  |     with_view_model( | ||||||
|  |         code_input->view, (CodeInputModel * model) { | ||||||
|  |             code_input_reset_model_input_data(model); | ||||||
|  |             model->ok_callback = ok_callback; | ||||||
|  |             model->fail_callback = fail_callback; | ||||||
|  |             model->callback_context = callback_context; | ||||||
|  | 
 | ||||||
|  |             model->ext_buffer = buffer; | ||||||
|  |             model->ext_buffer_length = buffer_length; | ||||||
|  |             model->state = (*buffer_length == 0) ? 1 : 0; | ||||||
|  |             model->ext_update = ext_update; | ||||||
|  | 
 | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Set code input header text | ||||||
|  |  *  | ||||||
|  |  * @param code_input code input instance | ||||||
|  |  * @param text text to be shown | ||||||
|  |  */ | ||||||
|  | void code_input_set_header_text(CodeInput* code_input, const char* text) { | ||||||
|  |     with_view_model( | ||||||
|  |         code_input->view, (CodeInputModel * model) { | ||||||
|  |             model->header = text; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
							
								
								
									
										91
									
								
								applications/gui/modules/code_input.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								applications/gui/modules/code_input.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | |||||||
|  | /**
 | ||||||
|  |  * @file code_input.h | ||||||
|  |  * GUI: CodeInput keyboard view module API | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | /** Code input anonymous structure  */ | ||||||
|  | typedef struct CodeInput CodeInput; | ||||||
|  | 
 | ||||||
|  | /** callback that is executed when entered code matches ext buffer */ | ||||||
|  | typedef void (*CodeInputOkCallback)(void* context); | ||||||
|  | 
 | ||||||
|  | /** callback that is executed when entered code does not matches ext buffer */ | ||||||
|  | typedef void (*CodeInputFailCallback)(void* context); | ||||||
|  | 
 | ||||||
|  | /** Allocate and initialize code input. This code input is used to enter codes.
 | ||||||
|  |  * | ||||||
|  |  * @return     CodeInput instance pointer | ||||||
|  |  */ | ||||||
|  | CodeInput* code_input_alloc(); | ||||||
|  | 
 | ||||||
|  | /** Deinitialize and free code input
 | ||||||
|  |  * | ||||||
|  |  * @param      code_input  Code input instance | ||||||
|  |  */ | ||||||
|  | void code_input_free(CodeInput* code_input); | ||||||
|  | 
 | ||||||
|  | /** Get code input view
 | ||||||
|  |  * | ||||||
|  |  * @param      code_input  code input instance | ||||||
|  |  * | ||||||
|  |  * @return     View instance that can be used for embedding | ||||||
|  |  */ | ||||||
|  | View* code_input_get_view(CodeInput* code_input); | ||||||
|  | 
 | ||||||
|  | /** Set code input result callback
 | ||||||
|  |  * | ||||||
|  |  * @param      code_input        code input instance | ||||||
|  |  * @param      ok_callback    ok callback fn | ||||||
|  |  * @param      fail_callback  fail callback fn | ||||||
|  |  * @param      callback_context  callback context | ||||||
|  |  * @param      buffer       buffer to use | ||||||
|  |  * @param      buffer_length       buffer length | ||||||
|  |  * @param      update  set true to update buffer  | ||||||
|  |  */ | ||||||
|  | void code_input_set_result_callback( | ||||||
|  |     CodeInput* code_input, | ||||||
|  |     CodeInputOkCallback ok_callback, | ||||||
|  |     CodeInputFailCallback fail_callback, | ||||||
|  |     void* callback_context, | ||||||
|  |     uint8_t* buffer, | ||||||
|  |     uint8_t* buffer_length, | ||||||
|  |     bool update); | ||||||
|  | 
 | ||||||
|  | /** Set code input header text
 | ||||||
|  |  * | ||||||
|  |  * @param      code_input  code input instance | ||||||
|  |  * @param      text        text to be shown | ||||||
|  |  */ | ||||||
|  | void code_input_set_header_text(CodeInput* code_input, const char* text); | ||||||
|  | 
 | ||||||
|  | /** Compare two buffers
 | ||||||
|  |  * | ||||||
|  |  * @param      in       buffer to compare to source | ||||||
|  |  * @param      len_in   length of input buffer | ||||||
|  |  * @param      src      source buffer | ||||||
|  |  * @param      len_src  length of insourceput buffer | ||||||
|  |  * @return     true if buffers match | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | bool code_input_compare(uint8_t* in, size_t len_in, uint8_t* src, size_t len_src); | ||||||
|  | 
 | ||||||
|  | /** Push input into the end of array
 | ||||||
|  |  * | ||||||
|  |  * @param      buffer   buffer | ||||||
|  |  * @param      length   length of buffer | ||||||
|  |  * @param      key      input key | ||||||
|  |  * @return     new length of input buffer | ||||||
|  |  */ | ||||||
|  | size_t code_input_push(uint8_t* buffer, size_t length, InputKey key); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
| @ -178,7 +178,7 @@ void menu_clean(Menu* menu) { | |||||||
|     furi_assert(menu); |     furi_assert(menu); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         menu->view, (MenuModel * model) { |         menu->view, (MenuModel * model) { | ||||||
|             MenuItemArray_clean(model->items); |             MenuItemArray_reset(model->items); | ||||||
|             model->position = 0; |             model->position = 0; | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
|  | |||||||
| @ -174,7 +174,7 @@ void submenu_clean(Submenu* submenu) { | |||||||
| 
 | 
 | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         submenu->view, (SubmenuModel * model) { |         submenu->view, (SubmenuModel * model) { | ||||||
|             SubmenuItemArray_clean(model->items); |             SubmenuItemArray_reset(model->items); | ||||||
|             model->position = 0; |             model->position = 0; | ||||||
|             model->window_position = 0; |             model->window_position = 0; | ||||||
|             model->header = NULL; |             model->header = NULL; | ||||||
|  | |||||||
							
								
								
									
										36
									
								
								applications/gui/modules/variable-item-list.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										36
									
								
								applications/gui/modules/variable-item-list.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -84,6 +84,40 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { | |||||||
|     elements_scrollbar(canvas, model->position, VariableItemArray_size(model->items)); |     elements_scrollbar(canvas, model->position, VariableItemArray_size(model->items)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index) { | ||||||
|  |     with_view_model( | ||||||
|  |         variable_item_list->view, (VariableItemListModel * model) { | ||||||
|  |             uint8_t position = index; | ||||||
|  |             if(position >= VariableItemArray_size(model->items)) { | ||||||
|  |                 position = 0; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             model->position = position; | ||||||
|  |             model->window_position = position; | ||||||
|  | 
 | ||||||
|  |             if(model->window_position > 0) { | ||||||
|  |                 model->window_position -= 1; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if(VariableItemArray_size(model->items) <= 4) { | ||||||
|  |                 model->window_position = 0; | ||||||
|  |             } else { | ||||||
|  |                 if(model->window_position >= (VariableItemArray_size(model->items) - 4)) { | ||||||
|  |                     model->window_position = (VariableItemArray_size(model->items) - 4); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list) { | ||||||
|  |     VariableItemListModel* model = view_get_model(variable_item_list->view); | ||||||
|  |     uint8_t idx = model->position; | ||||||
|  |     view_commit_model(variable_item_list->view, false); | ||||||
|  |     return idx; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static bool variable_item_list_input_callback(InputEvent* event, void* context) { | static bool variable_item_list_input_callback(InputEvent* event, void* context) { | ||||||
|     VariableItemList* variable_item_list = context; |     VariableItemList* variable_item_list = context; | ||||||
|     furi_assert(variable_item_list); |     furi_assert(variable_item_list); | ||||||
| @ -261,7 +295,7 @@ void variable_item_list_clean(VariableItemList* variable_item_list) { | |||||||
|                 VariableItemArray_next(it)) { |                 VariableItemArray_next(it)) { | ||||||
|                 string_clear(VariableItemArray_ref(it)->current_value_text); |                 string_clear(VariableItemArray_ref(it)->current_value_text); | ||||||
|             } |             } | ||||||
|             VariableItemArray_clean(model->items); |             VariableItemArray_reset(model->items); | ||||||
|             return false; |             return false; | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
|  | |||||||
| @ -70,6 +70,10 @@ void variable_item_list_set_enter_callback( | |||||||
|     VariableItemListEnterCallback callback, |     VariableItemListEnterCallback callback, | ||||||
|     void* context); |     void* context); | ||||||
| 
 | 
 | ||||||
|  | void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index); | ||||||
|  | 
 | ||||||
|  | uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list); | ||||||
|  | 
 | ||||||
| /** Set item current selected index
 | /** Set item current selected index
 | ||||||
|  * |  * | ||||||
|  * @param      item                 VariableItem* instance |  * @param      item                 VariableItem* instance | ||||||
|  | |||||||
| @ -81,7 +81,7 @@ void widget_clear(Widget* widget) { | |||||||
|                 element->free(element); |                 element->free(element); | ||||||
|                 ElementArray_next(it); |                 ElementArray_next(it); | ||||||
|             } |             } | ||||||
|             ElementArray_clean(model->element); |             ElementArray_reset(model->element); | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
| @ -146,6 +146,21 @@ void widget_add_string_element( | |||||||
|     widget_add_element(widget, string_element); |     widget_add_element(widget, string_element); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void widget_add_text_box_element( | ||||||
|  |     Widget* widget, | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     uint8_t width, | ||||||
|  |     uint8_t height, | ||||||
|  |     Align horizontal, | ||||||
|  |     Align vertical, | ||||||
|  |     const char* text) { | ||||||
|  |     furi_assert(widget); | ||||||
|  |     WidgetElement* text_box_element = | ||||||
|  |         widget_element_text_box_create(x, y, width, height, horizontal, vertical, text); | ||||||
|  |     widget_add_element(widget, text_box_element); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void widget_add_button_element( | void widget_add_button_element( | ||||||
|     Widget* widget, |     Widget* widget, | ||||||
|     GuiButtonType button_type, |     GuiButtonType button_type, | ||||||
|  | |||||||
| @ -75,6 +75,30 @@ void widget_add_string_element( | |||||||
|     Font font, |     Font font, | ||||||
|     const char* text); |     const char* text); | ||||||
| 
 | 
 | ||||||
|  | /** Add Text Box Element
 | ||||||
|  |  * | ||||||
|  |  * @param      widget      Widget instance | ||||||
|  |  * @param      x           x coordinate | ||||||
|  |  * @param      y           y coordinate | ||||||
|  |  * @param      width       width to fit text | ||||||
|  |  * @param      height      height to fit text | ||||||
|  |  * @param      horizontal  Align instance | ||||||
|  |  * @param      vertical    Align instance | ||||||
|  |  * @param[in]  text        Formatted text. The following formats are available: | ||||||
|  |  *                          "\e#Bold text\e#" - bold font is used | ||||||
|  |  *                          "\e*Monospaced text\e*" - monospaced font is used | ||||||
|  |  *                          "\e#Inversed text\e#" - white text on black background | ||||||
|  |  */ | ||||||
|  | void widget_add_text_box_element( | ||||||
|  |     Widget* widget, | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     uint8_t width, | ||||||
|  |     uint8_t height, | ||||||
|  |     Align horizontal, | ||||||
|  |     Align vertical, | ||||||
|  |     const char* text); | ||||||
|  | 
 | ||||||
| /** Add Button Element
 | /** Add Button Element
 | ||||||
|  * |  * | ||||||
|  * @param      widget       Widget instance |  * @param      widget       Widget instance | ||||||
|  | |||||||
| @ -52,6 +52,16 @@ WidgetElement* widget_element_string_create( | |||||||
|     Font font, |     Font font, | ||||||
|     const char* text); |     const char* text); | ||||||
| 
 | 
 | ||||||
|  | /** Create text box element */ | ||||||
|  | WidgetElement* widget_element_text_box_create( | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     uint8_t width, | ||||||
|  |     uint8_t height, | ||||||
|  |     Align horizontal, | ||||||
|  |     Align vertical, | ||||||
|  |     const char* text); | ||||||
|  | 
 | ||||||
| /** Create button element */ | /** Create button element */ | ||||||
| WidgetElement* widget_element_button_create( | WidgetElement* widget_element_button_create( | ||||||
|     GuiButtonType button_type, |     GuiButtonType button_type, | ||||||
|  | |||||||
| @ -0,0 +1,71 @@ | |||||||
|  | #include "widget_element_i.h" | ||||||
|  | #include <m-string.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t x; | ||||||
|  |     uint8_t y; | ||||||
|  |     uint8_t width; | ||||||
|  |     uint8_t height; | ||||||
|  |     Align horizontal; | ||||||
|  |     Align vertical; | ||||||
|  |     string_t text; | ||||||
|  | } GuiTextBoxModel; | ||||||
|  | 
 | ||||||
|  | static void gui_text_box_draw(Canvas* canvas, WidgetElement* element) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  |     furi_assert(element); | ||||||
|  |     GuiTextBoxModel* model = element->model; | ||||||
|  | 
 | ||||||
|  |     if(string_size(model->text)) { | ||||||
|  |         elements_text_box( | ||||||
|  |             canvas, | ||||||
|  |             model->x, | ||||||
|  |             model->y, | ||||||
|  |             model->width, | ||||||
|  |             model->height, | ||||||
|  |             model->horizontal, | ||||||
|  |             model->vertical, | ||||||
|  |             string_get_cstr(model->text)); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void gui_text_box_free(WidgetElement* gui_string) { | ||||||
|  |     furi_assert(gui_string); | ||||||
|  | 
 | ||||||
|  |     GuiTextBoxModel* model = gui_string->model; | ||||||
|  |     string_clear(model->text); | ||||||
|  |     free(gui_string->model); | ||||||
|  |     free(gui_string); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | WidgetElement* widget_element_text_box_create( | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     uint8_t width, | ||||||
|  |     uint8_t height, | ||||||
|  |     Align horizontal, | ||||||
|  |     Align vertical, | ||||||
|  |     const char* text) { | ||||||
|  |     furi_assert(text); | ||||||
|  | 
 | ||||||
|  |     // Allocate and init model
 | ||||||
|  |     GuiTextBoxModel* model = furi_alloc(sizeof(GuiTextBoxModel)); | ||||||
|  |     model->x = x; | ||||||
|  |     model->y = y; | ||||||
|  |     model->width = width; | ||||||
|  |     model->height = height; | ||||||
|  |     model->horizontal = horizontal; | ||||||
|  |     model->vertical = vertical; | ||||||
|  |     string_init_set_str(model->text, text); | ||||||
|  | 
 | ||||||
|  |     // Allocate and init Element
 | ||||||
|  |     WidgetElement* gui_string = furi_alloc(sizeof(WidgetElement)); | ||||||
|  |     gui_string->parent = NULL; | ||||||
|  |     gui_string->input = NULL; | ||||||
|  |     gui_string->draw = gui_text_box_draw; | ||||||
|  |     gui_string->free = gui_text_box_free; | ||||||
|  |     gui_string->model = model; | ||||||
|  | 
 | ||||||
|  |     return gui_string; | ||||||
|  | } | ||||||
| @ -1,5 +1,7 @@ | |||||||
| #include "view_dispatcher_i.h" | #include "view_dispatcher_i.h" | ||||||
| 
 | 
 | ||||||
|  | #define TAG "ViewDispatcher" | ||||||
|  | 
 | ||||||
| ViewDispatcher* view_dispatcher_alloc() { | ViewDispatcher* view_dispatcher_alloc() { | ||||||
|     ViewDispatcher* view_dispatcher = furi_alloc(sizeof(ViewDispatcher)); |     ViewDispatcher* view_dispatcher = furi_alloc(sizeof(ViewDispatcher)); | ||||||
| 
 | 
 | ||||||
| @ -236,8 +238,8 @@ void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* e | |||||||
|     } else if(event->type == InputTypeRelease) { |     } else if(event->type == InputTypeRelease) { | ||||||
|         view_dispatcher->ongoing_input &= ~key_bit; |         view_dispatcher->ongoing_input &= ~key_bit; | ||||||
|     } else if(!(view_dispatcher->ongoing_input & key_bit)) { |     } else if(!(view_dispatcher->ongoing_input & key_bit)) { | ||||||
|         FURI_LOG_W( |         FURI_LOG_D( | ||||||
|             "ViewDispatcher", |             TAG, | ||||||
|             "non-complementary input, discarding key: %s, type: %s, sequence: %p", |             "non-complementary input, discarding key: %s, type: %s, sequence: %p", | ||||||
|             input_get_key_name(event->key), |             input_get_key_name(event->key), | ||||||
|             input_get_type_name(event->type), |             input_get_type_name(event->type), | ||||||
| @ -275,8 +277,8 @@ void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* e | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } else if(view_dispatcher->ongoing_input_view && event->type == InputTypeRelease) { |     } else if(view_dispatcher->ongoing_input_view && event->type == InputTypeRelease) { | ||||||
|         FURI_LOG_W( |         FURI_LOG_D( | ||||||
|             "ViewDispatcher", |             TAG, | ||||||
|             "View changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view port", |             "View changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view port", | ||||||
|             view_dispatcher->ongoing_input_view, |             view_dispatcher->ongoing_input_view, | ||||||
|             view_dispatcher->current_view, |             view_dispatcher->current_view, | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| #include <callback-connector.h> | #include <callback-connector.h> | ||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
| #include <toolbox/path.h> | #include <toolbox/path.h> | ||||||
| #include <toolbox/flipper-file-cpp.h> | #include <flipper_file/flipper_file.h> | ||||||
| 
 | 
 | ||||||
| const char* iButtonApp::app_folder = "/any/ibutton"; | const char* iButtonApp::app_folder = "/any/ibutton"; | ||||||
| const char* iButtonApp::app_extension = ".ibtn"; | const char* iButtonApp::app_extension = ".ibtn"; | ||||||
| @ -48,8 +48,8 @@ iButtonApp::iButtonApp() | |||||||
| iButtonApp::~iButtonApp() { | iButtonApp::~iButtonApp() { | ||||||
|     for(std::map<Scene, iButtonScene*>::iterator it = scenes.begin(); it != scenes.end(); ++it) { |     for(std::map<Scene, iButtonScene*>::iterator it = scenes.begin(); it != scenes.end(); ++it) { | ||||||
|         delete it->second; |         delete it->second; | ||||||
|         scenes.erase(it); |  | ||||||
|     } |     } | ||||||
|  |     scenes.clear(); | ||||||
|     delete key_worker; |     delete key_worker; | ||||||
| 
 | 
 | ||||||
|     furi_hal_power_insomnia_exit(); |     furi_hal_power_insomnia_exit(); | ||||||
| @ -191,7 +191,7 @@ bool iButtonApp::save_key(const char* key_name) { | |||||||
|     // Create ibutton directory if necessary
 |     // Create ibutton directory if necessary
 | ||||||
|     make_app_folder(); |     make_app_folder(); | ||||||
| 
 | 
 | ||||||
|     FlipperFileCpp file(storage); |     FlipperFile* file = flipper_file_alloc(storage); | ||||||
|     string_t key_file_name; |     string_t key_file_name; | ||||||
|     bool result = false; |     bool result = false; | ||||||
|     string_init(key_file_name); |     string_init(key_file_name); | ||||||
| @ -207,27 +207,30 @@ bool iButtonApp::save_key(const char* key_name) { | |||||||
|         string_printf(key_file_name, "%s/%s%s", app_folder, key.get_name(), app_extension); |         string_printf(key_file_name, "%s/%s%s", app_folder, key.get_name(), app_extension); | ||||||
| 
 | 
 | ||||||
|         // Open file for write
 |         // Open file for write
 | ||||||
|         if(!file.new_write(string_get_cstr(key_file_name))) break; |         if(!flipper_file_open_always(file, string_get_cstr(key_file_name))) break; | ||||||
| 
 | 
 | ||||||
|         // Write header
 |         // Write header
 | ||||||
|         if(!file.write_header_cstr(iButtonApp::app_filetype, 1)) break; |         if(!flipper_file_write_header_cstr(file, iButtonApp::app_filetype, 1)) break; | ||||||
| 
 | 
 | ||||||
|         // Write key type
 |         // Write key type
 | ||||||
|         if(!file.write_comment_cstr("Key type can be Cyfral, Dallas or Metakom")) break; |         if(!flipper_file_write_comment_cstr(file, "Key type can be Cyfral, Dallas or Metakom")) | ||||||
|  |             break; | ||||||
|         const char* key_type = key.get_key_type_string_by_type(key.get_key_type()); |         const char* key_type = key.get_key_type_string_by_type(key.get_key_type()); | ||||||
|         if(!file.write_string_cstr("Key type", key_type)) break; |         if(!flipper_file_write_string_cstr(file, "Key type", key_type)) break; | ||||||
| 
 | 
 | ||||||
|         // Write data
 |         // Write data
 | ||||||
|         if(!file.write_comment_cstr( |         if(!flipper_file_write_comment_cstr( | ||||||
|                "Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8")) |                file, "Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8")) | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         if(!file.write_hex_array("Data", key.get_data(), key.get_type_data_size())) break; |         if(!flipper_file_write_hex(file, "Data", key.get_data(), key.get_type_data_size())) break; | ||||||
|         result = true; |         result = true; | ||||||
| 
 | 
 | ||||||
|     } while(false); |     } while(false); | ||||||
| 
 | 
 | ||||||
|     file.close(); |     flipper_file_close(file); | ||||||
|  |     flipper_file_free(file); | ||||||
|  | 
 | ||||||
|     string_clear(key_file_name); |     string_clear(key_file_name); | ||||||
| 
 | 
 | ||||||
|     if(!result) { |     if(!result) { | ||||||
| @ -238,28 +241,29 @@ bool iButtonApp::save_key(const char* key_name) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool iButtonApp::load_key_data(string_t key_path) { | bool iButtonApp::load_key_data(string_t key_path) { | ||||||
|     FlipperFileCpp file(storage); |     FlipperFile* file = flipper_file_alloc(storage); | ||||||
|     bool result = false; |     bool result = false; | ||||||
|     string_t data; |     string_t data; | ||||||
|     string_init(data); |     string_init(data); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         if(!file.open_read(string_get_cstr(key_path))) break; |         if(!flipper_file_open_existing(file, string_get_cstr(key_path))) break; | ||||||
| 
 | 
 | ||||||
|         // header
 |         // header
 | ||||||
|         uint32_t version; |         uint32_t version; | ||||||
|         if(!file.read_header(data, &version)) break; |         if(!flipper_file_read_header(file, data, &version)) break; | ||||||
|         if(string_cmp_str(data, iButtonApp::app_filetype) != 0) break; |         if(string_cmp_str(data, iButtonApp::app_filetype) != 0) break; | ||||||
|         if(version != 1) break; |         if(version != 1) break; | ||||||
| 
 | 
 | ||||||
|         // key type
 |         // key type
 | ||||||
|         iButtonKeyType type; |         iButtonKeyType type; | ||||||
|         if(!file.read_string("Key type", data)) break; |         if(!flipper_file_read_string(file, "Key type", data)) break; | ||||||
|         if(!key.get_key_type_by_type_string(string_get_cstr(data), &type)) break; |         if(!key.get_key_type_by_type_string(string_get_cstr(data), &type)) break; | ||||||
| 
 | 
 | ||||||
|         // key data
 |         // key data
 | ||||||
|         uint8_t key_data[IBUTTON_KEY_DATA_SIZE] = {0}; |         uint8_t key_data[IBUTTON_KEY_DATA_SIZE] = {0}; | ||||||
|         if(!file.read_hex_array("Data", key_data, key.get_type_data_size_by_type(type))) break; |         if(!flipper_file_read_hex(file, "Data", key_data, key.get_type_data_size_by_type(type))) | ||||||
|  |             break; | ||||||
| 
 | 
 | ||||||
|         key.set_type(type); |         key.set_type(type); | ||||||
|         key.set_data(key_data, IBUTTON_KEY_DATA_SIZE); |         key.set_data(key_data, IBUTTON_KEY_DATA_SIZE); | ||||||
| @ -267,7 +271,8 @@ bool iButtonApp::load_key_data(string_t key_path) { | |||||||
|         result = true; |         result = true; | ||||||
|     } while(false); |     } while(false); | ||||||
| 
 | 
 | ||||||
|     file.close(); |     flipper_file_close(file); | ||||||
|  |     flipper_file_free(file); | ||||||
|     string_clear(data); |     string_clear(data); | ||||||
| 
 | 
 | ||||||
|     if(!result) { |     if(!result) { | ||||||
|  | |||||||
| @ -28,11 +28,11 @@ void input_press_timer_callback(void* arg) { | |||||||
|     input_pin->press_counter++; |     input_pin->press_counter++; | ||||||
|     if(input_pin->press_counter == INPUT_LONG_PRESS_COUNTS) { |     if(input_pin->press_counter == INPUT_LONG_PRESS_COUNTS) { | ||||||
|         event.type = InputTypeLong; |         event.type = InputTypeLong; | ||||||
|         notify_pubsub(&input->event_pubsub, &event); |         furi_pubsub_publish(input->event_pubsub, &event); | ||||||
|     } else if(input_pin->press_counter > INPUT_LONG_PRESS_COUNTS) { |     } else if(input_pin->press_counter > INPUT_LONG_PRESS_COUNTS) { | ||||||
|         input_pin->press_counter--; |         input_pin->press_counter--; | ||||||
|         event.type = InputTypeRepeat; |         event.type = InputTypeRepeat; | ||||||
|         notify_pubsub(&input->event_pubsub, &event); |         furi_pubsub_publish(input->event_pubsub, &event); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -89,7 +89,7 @@ void input_cli_send(Cli* cli, string_t args, void* context) { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     // Publish input event
 |     // Publish input event
 | ||||||
|     notify_pubsub(&input->event_pubsub, &event); |     furi_pubsub_publish(input->event_pubsub, &event); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const char* input_get_key_name(InputKey key) { | const char* input_get_key_name(InputKey key) { | ||||||
| @ -120,8 +120,8 @@ const char* input_get_type_name(InputType type) { | |||||||
| int32_t input_srv() { | int32_t input_srv() { | ||||||
|     input = furi_alloc(sizeof(Input)); |     input = furi_alloc(sizeof(Input)); | ||||||
|     input->thread = osThreadGetId(); |     input->thread = osThreadGetId(); | ||||||
|     init_pubsub(&input->event_pubsub); |     input->event_pubsub = furi_pubsub_alloc(); | ||||||
|     furi_record_create("input_events", &input->event_pubsub); |     furi_record_create("input_events", input->event_pubsub); | ||||||
| 
 | 
 | ||||||
|     input->cli = furi_record_open("cli"); |     input->cli = furi_record_open("cli"); | ||||||
|     if(input->cli) { |     if(input->cli) { | ||||||
| @ -168,14 +168,14 @@ int32_t input_srv() { | |||||||
|                     input_timer_stop(input->pin_states[i].press_timer); |                     input_timer_stop(input->pin_states[i].press_timer); | ||||||
|                     if(input->pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) { |                     if(input->pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) { | ||||||
|                         event.type = InputTypeShort; |                         event.type = InputTypeShort; | ||||||
|                         notify_pubsub(&input->event_pubsub, &event); |                         furi_pubsub_publish(input->event_pubsub, &event); | ||||||
|                     } |                     } | ||||||
|                     input->pin_states[i].press_counter = 0; |                     input->pin_states[i].press_counter = 0; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 // Send Press/Release event
 |                 // Send Press/Release event
 | ||||||
|                 event.type = input->pin_states[i].state ? InputTypePress : InputTypeRelease; |                 event.type = input->pin_states[i].state ? InputTypePress : InputTypeRelease; | ||||||
|                 notify_pubsub(&input->event_pubsub, &event); |                 furi_pubsub_publish(input->event_pubsub, &event); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ typedef enum { | |||||||
|     InputTypeRepeat, /**< Repeat event, emmited with INPUT_REPEATE_PRESS period after InputTypeLong event */ |     InputTypeRepeat, /**< Repeat event, emmited with INPUT_REPEATE_PRESS period after InputTypeLong event */ | ||||||
| } InputType; | } InputType; | ||||||
| 
 | 
 | ||||||
| /** Input Event, dispatches with PubSub */ | /** Input Event, dispatches with FuriPubSub */ | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint32_t sequence; |     uint32_t sequence; | ||||||
|     InputKey key; |     InputKey key; | ||||||
|  | |||||||
| @ -6,8 +6,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "input.h" | #include "input.h" | ||||||
| #include <FreeRTOS.h> |  | ||||||
| #include <timers.h> |  | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
| @ -35,7 +33,7 @@ typedef struct { | |||||||
| /** Input state */ | /** Input state */ | ||||||
| typedef struct { | typedef struct { | ||||||
|     osThreadId_t thread; |     osThreadId_t thread; | ||||||
|     PubSub event_pubsub; |     FuriPubSub* event_pubsub; | ||||||
|     InputPinState* pin_states; |     InputPinState* pin_states; | ||||||
|     Cli* cli; |     Cli* cli; | ||||||
|     volatile uint32_t counter; |     volatile uint32_t counter; | ||||||
|  | |||||||
| @ -11,6 +11,8 @@ | |||||||
| #include <furi-hal-irda.h> | #include <furi-hal-irda.h> | ||||||
| #include <file-worker-cpp.h> | #include <file-worker-cpp.h> | ||||||
| 
 | 
 | ||||||
|  | #define TAG "IrdaFileParser" | ||||||
|  | 
 | ||||||
| bool IrdaAppFileParser::open_irda_file_read(const char* name) { | bool IrdaAppFileParser::open_irda_file_read(const char* name) { | ||||||
|     std::string full_filename; |     std::string full_filename; | ||||||
|     if(name[0] != '/') |     if(name[0] != '/') | ||||||
| @ -154,11 +156,7 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | |||||||
|     if(!irda_is_protocol_valid((IrdaProtocol)protocol)) { |     if(!irda_is_protocol_valid((IrdaProtocol)protocol)) { | ||||||
|         size_t end_of_str = MIN(str.find_last_not_of(" \t\r\n") + 1, (size_t)30); |         size_t end_of_str = MIN(str.find_last_not_of(" \t\r\n") + 1, (size_t)30); | ||||||
|         FURI_LOG_E( |         FURI_LOG_E( | ||||||
|             "IrdaFileParser", |             TAG, "Unknown protocol(\'%.*s...\'): \'%s\'", end_of_str, str.c_str(), protocol_name); | ||||||
|             "Unknown protocol(\'%.*s...\'): \'%s\'", |  | ||||||
|             end_of_str, |  | ||||||
|             str.c_str(), |  | ||||||
|             protocol_name); |  | ||||||
|         return nullptr; |         return nullptr; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -167,7 +165,7 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | |||||||
|     if(address != (address & address_mask)) { |     if(address != (address & address_mask)) { | ||||||
|         size_t end_of_str = MIN(str.find_last_not_of(" \t\r\n") + 1, (size_t)30); |         size_t end_of_str = MIN(str.find_last_not_of(" \t\r\n") + 1, (size_t)30); | ||||||
|         FURI_LOG_E( |         FURI_LOG_E( | ||||||
|             "IrdaFileParser", |             TAG, | ||||||
|             "Signal(\'%.*s...\'): address is too long (mask for this protocol is 0x%08X): 0x%X", |             "Signal(\'%.*s...\'): address is too long (mask for this protocol is 0x%08X): 0x%X", | ||||||
|             end_of_str, |             end_of_str, | ||||||
|             str.c_str(), |             str.c_str(), | ||||||
| @ -181,7 +179,7 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | |||||||
|     if(command != (command & command_mask)) { |     if(command != (command & command_mask)) { | ||||||
|         size_t end_of_str = MIN(str.find_last_not_of(" \t\r\n") + 1, (size_t)30); |         size_t end_of_str = MIN(str.find_last_not_of(" \t\r\n") + 1, (size_t)30); | ||||||
|         FURI_LOG_E( |         FURI_LOG_E( | ||||||
|             "IrdaFileParser", |             TAG, | ||||||
|             "Signal(\'%.*s...\'): command is too long (mask for this protocol is 0x%08X): 0x%X", |             "Signal(\'%.*s...\'): command is too long (mask for this protocol is 0x%08X): 0x%X", | ||||||
|             end_of_str, |             end_of_str, | ||||||
|             str.c_str(), |             str.c_str(), | ||||||
| @ -256,7 +254,7 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | |||||||
|     if((frequency < IRDA_MIN_FREQUENCY) || (frequency > IRDA_MAX_FREQUENCY)) { |     if((frequency < IRDA_MIN_FREQUENCY) || (frequency > IRDA_MAX_FREQUENCY)) { | ||||||
|         size_t end_of_str = MIN(string.find_last_not_of(" \t\r\n") + 1, (size_t)30); |         size_t end_of_str = MIN(string.find_last_not_of(" \t\r\n") + 1, (size_t)30); | ||||||
|         FURI_LOG_E( |         FURI_LOG_E( | ||||||
|             "IrdaFileParser", |             TAG, | ||||||
|             "RAW signal(\'%.*s...\'): frequency is out of bounds (%ld-%ld): %ld", |             "RAW signal(\'%.*s...\'): frequency is out of bounds (%ld-%ld): %ld", | ||||||
|             end_of_str, |             end_of_str, | ||||||
|             string.c_str(), |             string.c_str(), | ||||||
| @ -269,7 +267,7 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | |||||||
|     if((duty_cycle == 0) || (duty_cycle > 100)) { |     if((duty_cycle == 0) || (duty_cycle > 100)) { | ||||||
|         size_t end_of_str = MIN(string.find_last_not_of(" \t\r\n") + 1, (size_t)30); |         size_t end_of_str = MIN(string.find_last_not_of(" \t\r\n") + 1, (size_t)30); | ||||||
|         FURI_LOG_E( |         FURI_LOG_E( | ||||||
|             "IrdaFileParser", |             TAG, | ||||||
|             "RAW signal(\'%.*s...\'): duty cycle is out of bounds (0-100): %ld", |             "RAW signal(\'%.*s...\'): duty cycle is out of bounds (0-100): %ld", | ||||||
|             end_of_str, |             end_of_str, | ||||||
|             string.c_str(), |             string.c_str(), | ||||||
| @ -283,8 +281,7 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | |||||||
|     if(last_valid_ch != std::string_view::npos) { |     if(last_valid_ch != std::string_view::npos) { | ||||||
|         str.remove_suffix(str.size() - last_valid_ch - 1); |         str.remove_suffix(str.size() - last_valid_ch - 1); | ||||||
|     } else { |     } else { | ||||||
|         FURI_LOG_E( |         FURI_LOG_E(TAG, "RAW signal(\'%.*s\'): no timings", header_len, string.c_str()); | ||||||
|             "IrdaFileParser", "RAW signal(\'%.*s\'): no timings", header_len, string.c_str()); |  | ||||||
|         return nullptr; |         return nullptr; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -303,7 +300,7 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | |||||||
|         parsed = std::sscanf(str.data(), "%9s", buf); |         parsed = std::sscanf(str.data(), "%9s", buf); | ||||||
|         if(parsed != 1) { |         if(parsed != 1) { | ||||||
|             FURI_LOG_E( |             FURI_LOG_E( | ||||||
|                 "IrdaFileParser", |                 TAG, | ||||||
|                 "RAW signal(\'%.*s...\'): failed on timing[%ld] \'%*s\'", |                 "RAW signal(\'%.*s...\'): failed on timing[%ld] \'%*s\'", | ||||||
|                 header_len, |                 header_len, | ||||||
|                 string.c_str(), |                 string.c_str(), | ||||||
| @ -318,7 +315,7 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | |||||||
|         int value = atoi(buf); |         int value = atoi(buf); | ||||||
|         if(value <= 0) { |         if(value <= 0) { | ||||||
|             FURI_LOG_E( |             FURI_LOG_E( | ||||||
|                 "IrdaFileParser", |                 TAG, | ||||||
|                 "RAW signal(\'%.*s...\'): failed on timing[%ld] \'%s\'", |                 "RAW signal(\'%.*s...\'): failed on timing[%ld] \'%s\'", | ||||||
|                 header_len, |                 header_len, | ||||||
|                 string.c_str(), |                 string.c_str(), | ||||||
| @ -330,7 +327,7 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | |||||||
| 
 | 
 | ||||||
|         if(raw_signal.timings_cnt >= max_raw_timings_in_signal) { |         if(raw_signal.timings_cnt >= max_raw_timings_in_signal) { | ||||||
|             FURI_LOG_E( |             FURI_LOG_E( | ||||||
|                 "IrdaFileParser", |                 TAG, | ||||||
|                 "RAW signal(\'%.*s...\'): too much timings (max %ld)", |                 "RAW signal(\'%.*s...\'): too much timings (max %ld)", | ||||||
|                 header_len, |                 header_len, | ||||||
|                 string.c_str(), |                 string.c_str(), | ||||||
|  | |||||||
| @ -211,10 +211,12 @@ void IrdaApp::notify_red_blink() { | |||||||
|     notification_message(notification, &sequence_blink_red_10); |     notification_message(notification, &sequence_blink_red_10); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void IrdaApp::notify_space_blink() { | void IrdaApp::notify_sent_just_learnt() { | ||||||
|     static const NotificationSequence sequence = { |     static const NotificationSequence sequence = { | ||||||
|         &message_green_0, |         &message_green_0, | ||||||
|  |         &message_vibro_on, | ||||||
|         &message_delay_50, |         &message_delay_50, | ||||||
|  |         &message_vibro_off, | ||||||
|         &message_green_255, |         &message_green_255, | ||||||
|         &message_do_not_reset, |         &message_do_not_reset, | ||||||
|         NULL, |         NULL, | ||||||
| @ -261,10 +263,6 @@ void IrdaApp::notify_blink_green() { | |||||||
|     notification_message(notification, &sequence); |     notification_message(notification, &sequence); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void IrdaApp::notify_double_vibro() { |  | ||||||
|     notification_message(notification, &sequence_double_vibro); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void IrdaApp::notify_green_on() { | void IrdaApp::notify_green_on() { | ||||||
|     notification_message(notification, &sequence_set_only_green_255); |     notification_message(notification, &sequence_set_only_green_255); | ||||||
| } | } | ||||||
|  | |||||||
| @ -77,8 +77,7 @@ public: | |||||||
| 
 | 
 | ||||||
|     void notify_success(); |     void notify_success(); | ||||||
|     void notify_red_blink(); |     void notify_red_blink(); | ||||||
|     void notify_space_blink(); |     void notify_sent_just_learnt(); | ||||||
|     void notify_double_vibro(); |  | ||||||
|     void notify_green_on(); |     void notify_green_on(); | ||||||
|     void notify_green_off(); |     void notify_green_off(); | ||||||
|     void notify_click(); |     void notify_click(); | ||||||
|  | |||||||
| @ -23,16 +23,24 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) { | |||||||
| 
 | 
 | ||||||
|     if(!signal.is_raw()) { |     if(!signal.is_raw()) { | ||||||
|         auto message = &signal.get_message(); |         auto message = &signal.get_message(); | ||||||
|  |         uint8_t adr_digits = ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4); | ||||||
|  |         uint8_t cmd_digits = ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4); | ||||||
|  |         uint8_t max_digits = MAX(adr_digits, cmd_digits); | ||||||
|  |         max_digits = MIN(max_digits, 7); | ||||||
|  |         size_t label_x_offset = 63 + (7 - max_digits) * 3; | ||||||
|  | 
 | ||||||
|         app->set_text_store(0, "%s", irda_get_protocol_name(message->protocol)); |         app->set_text_store(0, "%s", irda_get_protocol_name(message->protocol)); | ||||||
|         app->set_text_store( |         app->set_text_store( | ||||||
|             1, |             1, | ||||||
|             "A: 0x%0*lX\nC: 0x%0*lX\n", |             "A: 0x%0*lX\nC: 0x%0*lX\n", | ||||||
|             ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4), |             adr_digits, | ||||||
|             message->address, |             message->address, | ||||||
|             ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), |             cmd_digits, | ||||||
|             message->command); |             message->command); | ||||||
|         dialog_ex_set_header(dialog_ex, app->get_text_store(0), 95, 10, AlignCenter, AlignCenter); | 
 | ||||||
|         dialog_ex_set_text(dialog_ex, app->get_text_store(1), 75, 23, AlignLeft, AlignTop); |         dialog_ex_set_header(dialog_ex, app->get_text_store(0), 95, 7, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, app->get_text_store(1), label_x_offset, 34, AlignLeft, AlignCenter); | ||||||
|     } else { |     } else { | ||||||
|         dialog_ex_set_header(dialog_ex, "Unknown", 95, 10, AlignCenter, AlignCenter); |         dialog_ex_set_header(dialog_ex, "Unknown", 95, 10, AlignCenter, AlignCenter); | ||||||
|         app->set_text_store(0, "%d samples", signal.get_raw_signal().timings_cnt); |         app->set_text_store(0, "%d samples", signal.get_raw_signal().timings_cnt); | ||||||
| @ -42,7 +50,7 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) { | |||||||
|     dialog_ex_set_left_button_text(dialog_ex, "Retry"); |     dialog_ex_set_left_button_text(dialog_ex, "Retry"); | ||||||
|     dialog_ex_set_right_button_text(dialog_ex, "Save"); |     dialog_ex_set_right_button_text(dialog_ex, "Save"); | ||||||
|     dialog_ex_set_center_button_text(dialog_ex, "Send"); |     dialog_ex_set_center_button_text(dialog_ex, "Send"); | ||||||
|     dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinExcited_64x63); |     dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63); | ||||||
|     dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); |     dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); | ||||||
|     dialog_ex_set_context(dialog_ex, app); |     dialog_ex_set_context(dialog_ex, app); | ||||||
| 
 | 
 | ||||||
| @ -62,7 +70,7 @@ bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) { | |||||||
|             app->switch_to_next_scene_without_saving(IrdaApp::Scene::Learn); |             app->switch_to_next_scene_without_saving(IrdaApp::Scene::Learn); | ||||||
|             break; |             break; | ||||||
|         case DialogExResultCenter: { |         case DialogExResultCenter: { | ||||||
|             app->notify_space_blink(); |             app->notify_sent_just_learnt(); | ||||||
|             auto signal = app->get_received_signal(); |             auto signal = app->get_received_signal(); | ||||||
|             signal.transmit(); |             signal.transmit(); | ||||||
|             break; |             break; | ||||||
|  | |||||||
| @ -39,10 +39,6 @@ void IrdaAppSceneLearn::on_enter(IrdaApp* app) { | |||||||
|         popup, "Point the remote at IR port\nand push the button", 5, 10, AlignLeft, AlignCenter); |         popup, "Point the remote at IR port\nand push the button", 5, 10, AlignLeft, AlignCenter); | ||||||
|     popup_set_callback(popup, NULL); |     popup_set_callback(popup, NULL); | ||||||
| 
 | 
 | ||||||
|     if(app->get_learn_new_remote()) { |  | ||||||
|         app->notify_double_vibro(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); |     view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -10,8 +10,9 @@ RfidTimerEmulator::~RfidTimerEmulator() { | |||||||
| 
 | 
 | ||||||
|     for(it = encoders.begin(); it != encoders.end(); ++it) { |     for(it = encoders.begin(); it != encoders.end(); ++it) { | ||||||
|         delete it->second; |         delete it->second; | ||||||
|         encoders.erase(it); |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     encoders.clear(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RfidTimerEmulator::start(LfrfidKeyType type, const uint8_t* data, uint8_t data_size) { | void RfidTimerEmulator::start(LfrfidKeyType type, const uint8_t* data, uint8_t data_size) { | ||||||
|  | |||||||
| @ -16,8 +16,8 @@ | |||||||
| #include "scene/lfrfid-app-scene-delete-confirm.h" | #include "scene/lfrfid-app-scene-delete-confirm.h" | ||||||
| #include "scene/lfrfid-app-scene-delete-success.h" | #include "scene/lfrfid-app-scene-delete-success.h" | ||||||
| 
 | 
 | ||||||
| #include <lib/toolbox/path.h> | #include <toolbox/path.h> | ||||||
| #include <lib/toolbox/flipper-file-cpp.h> | #include <flipper_file/flipper_file.h> | ||||||
| 
 | 
 | ||||||
| const char* LfRfidApp::app_folder = "/any/lfrfid"; | const char* LfRfidApp::app_folder = "/any/lfrfid"; | ||||||
| const char* LfRfidApp::app_extension = ".rfid"; | const char* LfRfidApp::app_extension = ".rfid"; | ||||||
| @ -119,17 +119,17 @@ bool LfRfidApp::delete_key(RfidKey* key) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { | bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { | ||||||
|     FlipperFileCpp file(storage); |     FlipperFile* file = flipper_file_alloc(storage); | ||||||
|     bool result = false; |     bool result = false; | ||||||
|     string_t str_result; |     string_t str_result; | ||||||
|     string_init(str_result); |     string_init(str_result); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         if(!file.open_read(path)) break; |         if(!flipper_file_open_existing(file, path)) break; | ||||||
| 
 | 
 | ||||||
|         // header
 |         // header
 | ||||||
|         uint32_t version; |         uint32_t version; | ||||||
|         if(!file.read_header(str_result, &version)) break; |         if(!flipper_file_read_header(file, str_result, &version)) break; | ||||||
|         if(string_cmp_str(str_result, app_filetype) != 0) break; |         if(string_cmp_str(str_result, app_filetype) != 0) break; | ||||||
|         if(version != 1) break; |         if(version != 1) break; | ||||||
| 
 | 
 | ||||||
| @ -137,13 +137,13 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { | |||||||
|         LfrfidKeyType type; |         LfrfidKeyType type; | ||||||
|         RfidKey loaded_key; |         RfidKey loaded_key; | ||||||
| 
 | 
 | ||||||
|         if(!file.read_string("Key type", str_result)) break; |         if(!flipper_file_read_string(file, "Key type", str_result)) break; | ||||||
|         if(!lfrfid_key_get_string_type(string_get_cstr(str_result), &type)) break; |         if(!lfrfid_key_get_string_type(string_get_cstr(str_result), &type)) break; | ||||||
|         loaded_key.set_type(type); |         loaded_key.set_type(type); | ||||||
| 
 | 
 | ||||||
|         // key data
 |         // key data
 | ||||||
|         uint8_t key_data[loaded_key.get_type_data_count()] = {}; |         uint8_t key_data[loaded_key.get_type_data_count()] = {}; | ||||||
|         if(!file.read_hex_array("Data", key_data, loaded_key.get_type_data_count())) break; |         if(!flipper_file_read_hex(file, "Data", key_data, loaded_key.get_type_data_count())) break; | ||||||
|         loaded_key.set_data(key_data, loaded_key.get_type_data_count()); |         loaded_key.set_data(key_data, loaded_key.get_type_data_count()); | ||||||
| 
 | 
 | ||||||
|         path_extract_filename_no_ext(path, str_result); |         path_extract_filename_no_ext(path, str_result); | ||||||
| @ -153,7 +153,8 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { | |||||||
|         result = true; |         result = true; | ||||||
|     } while(0); |     } while(0); | ||||||
| 
 | 
 | ||||||
|     file.close(); |     flipper_file_close(file); | ||||||
|  |     flipper_file_free(file); | ||||||
|     string_clear(str_result); |     string_clear(str_result); | ||||||
| 
 | 
 | ||||||
|     if(!result) { |     if(!result) { | ||||||
| @ -164,21 +165,27 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::save_key_data(const char* path, RfidKey* key) { | bool LfRfidApp::save_key_data(const char* path, RfidKey* key) { | ||||||
|     FlipperFileCpp file(storage); |     FlipperFile* file = flipper_file_alloc(storage); | ||||||
|     bool result = false; |     bool result = false; | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         if(!file.new_write(path)) break; |         if(!flipper_file_open_always(file, path)) break; | ||||||
|         if(!file.write_header_cstr(app_filetype, 1)) break; |         if(!flipper_file_write_header_cstr(file, app_filetype, 1)) break; | ||||||
|         if(!file.write_comment_cstr("Key type can be EM4100, H10301 or I40134")) break; |         if(!flipper_file_write_comment_cstr(file, "Key type can be EM4100, H10301 or I40134")) | ||||||
|         if(!file.write_string_cstr("Key type", lfrfid_key_get_type_string(key->get_type()))) break; |             break; | ||||||
|         if(!file.write_comment_cstr("Data size for EM4100 is 5, for H10301 is 3, for I40134 is 3")) |         if(!flipper_file_write_string_cstr( | ||||||
|  |                file, "Key type", lfrfid_key_get_type_string(key->get_type()))) | ||||||
|  |             break; | ||||||
|  |         if(!flipper_file_write_comment_cstr( | ||||||
|  |                file, "Data size for EM4100 is 5, for H10301 is 3, for I40134 is 3")) | ||||||
|  |             break; | ||||||
|  |         if(!flipper_file_write_hex(file, "Data", key->get_data(), key->get_type_data_count())) | ||||||
|             break; |             break; | ||||||
|         if(!file.write_hex_array("Data", key->get_data(), key->get_type_data_count())) break; |  | ||||||
|         result = true; |         result = true; | ||||||
|     } while(0); |     } while(0); | ||||||
| 
 | 
 | ||||||
|     file.close(); |     flipper_file_close(file); | ||||||
|  |     flipper_file_free(file); | ||||||
| 
 | 
 | ||||||
|     if(!result) { |     if(!result) { | ||||||
|         dialog_message_show_storage_error(dialogs, "Cannot save\nkey file"); |         dialog_message_show_storage_error(dialogs, "Cannot save\nkey file"); | ||||||
|  | |||||||
							
								
								
									
										36
									
								
								applications/loader/loader.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										36
									
								
								applications/loader/loader.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -1,6 +1,8 @@ | |||||||
| #include "loader/loader.h" | #include "loader/loader.h" | ||||||
| #include "loader_i.h" | #include "loader_i.h" | ||||||
| 
 | 
 | ||||||
|  | #define TAG "LoaderSrv" | ||||||
|  | 
 | ||||||
| #define LOADER_THREAD_FLAG_SHOW_MENU (1 << 0) | #define LOADER_THREAD_FLAG_SHOW_MENU (1 << 0) | ||||||
| #define LOADER_THREAD_FLAG_ALL (LOADER_THREAD_FLAG_SHOW_MENU) | #define LOADER_THREAD_FLAG_ALL (LOADER_THREAD_FLAG_SHOW_MENU) | ||||||
| 
 | 
 | ||||||
| @ -15,15 +17,13 @@ static void loader_menu_callback(void* _ctx, uint32_t index) { | |||||||
|     if(!loader_lock(loader_instance)) return; |     if(!loader_lock(loader_instance)) return; | ||||||
| 
 | 
 | ||||||
|     if(furi_thread_get_state(loader_instance->thread) != FuriThreadStateStopped) { |     if(furi_thread_get_state(loader_instance->thread) != FuriThreadStateStopped) { | ||||||
|         FURI_LOG_E( |         FURI_LOG_E(TAG, "Can't start app. %s is running", loader_instance->current_app->name); | ||||||
|             LOADER_LOG_TAG, "Can't start app. %s is running", loader_instance->current_app->name); |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     furi_hal_power_insomnia_enter(); |     furi_hal_power_insomnia_enter(); | ||||||
|     loader_instance->current_app = flipper_app; |     loader_instance->current_app = flipper_app; | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I( |     FURI_LOG_I(TAG, "Starting furi application: %s", loader_instance->current_app->name); | ||||||
|         LOADER_LOG_TAG, "Starting furi application: %s", loader_instance->current_app->name); |  | ||||||
|     furi_thread_set_name(loader_instance->thread, flipper_app->name); |     furi_thread_set_name(loader_instance->thread, flipper_app->name); | ||||||
|     furi_thread_set_stack_size(loader_instance->thread, flipper_app->stack_size); |     furi_thread_set_stack_size(loader_instance->thread, flipper_app->stack_size); | ||||||
|     furi_thread_set_context(loader_instance->thread, NULL); |     furi_thread_set_context(loader_instance->thread, NULL); | ||||||
| @ -79,14 +79,14 @@ LoaderStatus loader_start(Loader* instance, const char* name, const char* args) | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(!flipper_app) { |     if(!flipper_app) { | ||||||
|         FURI_LOG_E(LOADER_LOG_TAG, "Can't find application with name %s", name); |         FURI_LOG_E(TAG, "Can't find application with name %s", name); | ||||||
|         return LoaderStatusErrorUnknownApp; |         return LoaderStatusErrorUnknownApp; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     bool locked = loader_lock(instance); |     bool locked = loader_lock(instance); | ||||||
| 
 | 
 | ||||||
|     if(!locked || (furi_thread_get_state(instance->thread) != FuriThreadStateStopped)) { |     if(!locked || (furi_thread_get_state(instance->thread) != FuriThreadStateStopped)) { | ||||||
|         FURI_LOG_E(LOADER_LOG_TAG, "Can't start app. %s is running", instance->current_app->name); |         FURI_LOG_E(TAG, "Can't start app. %s is running", instance->current_app->name); | ||||||
|         /* no need to call loader_unlock() - it is called as soon as application stops */ |         /* no need to call loader_unlock() - it is called as soon as application stops */ | ||||||
|         return LoaderStatusErrorAppStarted; |         return LoaderStatusErrorAppStarted; | ||||||
|     } |     } | ||||||
| @ -97,10 +97,10 @@ LoaderStatus loader_start(Loader* instance, const char* name, const char* args) | |||||||
|         string_set_str(instance->args, args); |         string_set_str(instance->args, args); | ||||||
|         string_strim(instance->args); |         string_strim(instance->args); | ||||||
|         thread_args = (void*)string_get_cstr(instance->args); |         thread_args = (void*)string_get_cstr(instance->args); | ||||||
|         FURI_LOG_I(LOADER_LOG_TAG, "Start %s app with args: %s", name, args); |         FURI_LOG_I(TAG, "Start %s app with args: %s", name, args); | ||||||
|     } else { |     } else { | ||||||
|         string_clean(instance->args); |         string_reset(instance->args); | ||||||
|         FURI_LOG_I(LOADER_LOG_TAG, "Start %s app with no args", name); |         FURI_LOG_I(TAG, "Start %s app with no args", name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     furi_thread_set_name(instance->thread, flipper_app->name); |     furi_thread_set_name(instance->thread, flipper_app->name); | ||||||
| @ -140,7 +140,7 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con | |||||||
|     Loader* instance = context; |     Loader* instance = context; | ||||||
| 
 | 
 | ||||||
|     if(thread_state == FuriThreadStateRunning) { |     if(thread_state == FuriThreadStateRunning) { | ||||||
|         instance->free_heap_size = xPortGetFreeHeapSize(); |         instance->free_heap_size = memmgr_get_free_heap(); | ||||||
|     } else if(thread_state == FuriThreadStateStopped) { |     } else if(thread_state == FuriThreadStateStopped) { | ||||||
|         /*
 |         /*
 | ||||||
|          * Current Leak Sanitizer assumes that memory is allocated and freed |          * Current Leak Sanitizer assumes that memory is allocated and freed | ||||||
| @ -153,9 +153,9 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con | |||||||
|          * both values should be taken into account. |          * both values should be taken into account. | ||||||
|          */ |          */ | ||||||
|         delay(20); |         delay(20); | ||||||
|         int heap_diff = instance->free_heap_size - xPortGetFreeHeapSize(); |         int heap_diff = instance->free_heap_size - memmgr_get_free_heap(); | ||||||
|         FURI_LOG_I( |         FURI_LOG_I( | ||||||
|             LOADER_LOG_TAG, |             TAG, | ||||||
|             "Application thread stopped. Heap allocation balance: %d. Thread allocation balance: %d.", |             "Application thread stopped. Heap allocation balance: %d. Thread allocation balance: %d.", | ||||||
|             heap_diff, |             heap_diff, | ||||||
|             furi_thread_get_heap_size(instance->thread)); |             furi_thread_get_heap_size(instance->thread)); | ||||||
| @ -266,7 +266,7 @@ static void loader_add_cli_command(FlipperApplication* app) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_build_menu() { | static void loader_build_menu() { | ||||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Building main menu"); |     FURI_LOG_I(TAG, "Building main menu"); | ||||||
|     size_t i; |     size_t i; | ||||||
|     for(i = 0; i < FLIPPER_APPS_COUNT; i++) { |     for(i = 0; i < FLIPPER_APPS_COUNT; i++) { | ||||||
|         loader_add_cli_command((FlipperApplication*)&FLIPPER_APPS[i]); |         loader_add_cli_command((FlipperApplication*)&FLIPPER_APPS[i]); | ||||||
| @ -300,7 +300,7 @@ static void loader_build_menu() { | |||||||
|         loader_submenu_callback, |         loader_submenu_callback, | ||||||
|         (void*)LoaderMenuViewSettings); |         (void*)LoaderMenuViewSettings); | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Building plugins menu"); |     FURI_LOG_I(TAG, "Building plugins menu"); | ||||||
|     for(i = 0; i < FLIPPER_PLUGINS_COUNT; i++) { |     for(i = 0; i < FLIPPER_PLUGINS_COUNT; i++) { | ||||||
|         loader_add_cli_command((FlipperApplication*)&FLIPPER_PLUGINS[i]); |         loader_add_cli_command((FlipperApplication*)&FLIPPER_PLUGINS[i]); | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
| @ -311,7 +311,7 @@ static void loader_build_menu() { | |||||||
|             (void*)&FLIPPER_PLUGINS[i]); |             (void*)&FLIPPER_PLUGINS[i]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Building debug menu"); |     FURI_LOG_I(TAG, "Building debug menu"); | ||||||
|     for(i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { |     for(i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { | ||||||
|         loader_add_cli_command((FlipperApplication*)&FLIPPER_DEBUG_APPS[i]); |         loader_add_cli_command((FlipperApplication*)&FLIPPER_DEBUG_APPS[i]); | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
| @ -322,7 +322,7 @@ static void loader_build_menu() { | |||||||
|             (void*)&FLIPPER_DEBUG_APPS[i]); |             (void*)&FLIPPER_DEBUG_APPS[i]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Building settings menu"); |     FURI_LOG_I(TAG, "Building settings menu"); | ||||||
|     for(i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { |     for(i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
|             loader_instance->settings_menu, |             loader_instance->settings_menu, | ||||||
| @ -339,7 +339,7 @@ void loader_show_menu() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int32_t loader_srv(void* p) { | int32_t loader_srv(void* p) { | ||||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Starting"); |     FURI_LOG_I(TAG, "Starting"); | ||||||
| 
 | 
 | ||||||
|     loader_instance = loader_alloc(); |     loader_instance = loader_alloc(); | ||||||
| 
 | 
 | ||||||
| @ -350,7 +350,7 @@ int32_t loader_srv(void* p) { | |||||||
|         FLIPPER_ON_SYSTEM_START[i](); |         FLIPPER_ON_SYSTEM_START[i](); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Started"); |     FURI_LOG_I(TAG, "Started"); | ||||||
| 
 | 
 | ||||||
|     furi_record_create("loader", loader_instance); |     furi_record_create("loader", loader_instance); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -12,8 +12,6 @@ | |||||||
| #include <applications.h> | #include <applications.h> | ||||||
| #include <assets_icons.h> | #include <assets_icons.h> | ||||||
| 
 | 
 | ||||||
| #define LOADER_LOG_TAG "loader" |  | ||||||
| 
 |  | ||||||
| struct Loader { | struct Loader { | ||||||
|     osThreadId_t loader_thread; |     osThreadId_t loader_thread; | ||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
|  | |||||||
| @ -1,49 +1,80 @@ | |||||||
| #include "nfc_emv_parser.h" | #include "nfc_emv_parser.h" | ||||||
|  | #include <lib/flipper_file/flipper_file.h> | ||||||
| 
 | 
 | ||||||
| #include <file-worker.h> | static const char* nfc_resources_header = "Flipper EMV resources"; | ||||||
|  | static const uint32_t nfc_resources_file_version = 1; | ||||||
| 
 | 
 | ||||||
| static bool | static bool nfc_emv_parser_search_data( | ||||||
|     nfc_emv_parser_get_value(const char* file_path, string_t key, char delimiter, string_t value) { |     Storage* storage, | ||||||
|     bool found = false; |     const char* file_name, | ||||||
|     FileWorker* file_worker = file_worker_alloc(true); |     string_t key, | ||||||
|  |     string_t data) { | ||||||
|  |     bool parsed = false; | ||||||
|  |     FlipperFile* file = flipper_file_alloc(storage); | ||||||
|  |     string_t temp_str; | ||||||
|  |     string_init(temp_str); | ||||||
| 
 | 
 | ||||||
|     if(file_worker_open(file_worker, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { |     do { | ||||||
|         if(file_worker_get_value_from_key(file_worker, key, delimiter, value)) { |         // Open file
 | ||||||
|             found = true; |         if(!flipper_file_open_existing(file, file_name)) break; | ||||||
|         } |         // Read file header and version
 | ||||||
|  |         uint32_t version = 0; | ||||||
|  |         if(!flipper_file_read_header(file, temp_str, &version)) break; | ||||||
|  |         if(string_cmp_str(temp_str, nfc_resources_header) || | ||||||
|  |            (version != nfc_resources_file_version)) | ||||||
|  |             break; | ||||||
|  |         if(!flipper_file_read_string(file, string_get_cstr(key), data)) break; | ||||||
|  |         parsed = true; | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     string_clear(temp_str); | ||||||
|  |     flipper_file_free(file); | ||||||
|  |     return parsed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|     file_worker_close(file_worker); | bool nfc_emv_parser_get_aid_name( | ||||||
|     file_worker_free(file_worker); |     Storage* storage, | ||||||
|     return found; |     uint8_t* aid, | ||||||
| } |     uint8_t aid_len, | ||||||
| 
 |     string_t aid_name) { | ||||||
| bool nfc_emv_parser_get_aid_name(uint8_t* aid, uint8_t aid_len, string_t aid_name) { |     furi_assert(storage); | ||||||
|     bool result = false; |     bool parsed = false; | ||||||
|     string_t key; |     string_t key; | ||||||
|     string_init(key); |     string_init(key); | ||||||
|     for(uint8_t i = 0; i < aid_len; i++) { |     for(uint8_t i = 0; i < aid_len; i++) { | ||||||
|         string_cat_printf(key, "%02X", aid[i]); |         string_cat_printf(key, "%02X", aid[i]); | ||||||
|     } |     } | ||||||
|     result = nfc_emv_parser_get_value("/ext/nfc/emv/aid.nfc", key, ' ', aid_name); |     if(nfc_emv_parser_search_data(storage, "/ext/nfc/emv/aid.nfc", key, aid_name)) { | ||||||
|  |         parsed = true; | ||||||
|  |     } | ||||||
|     string_clear(key); |     string_clear(key); | ||||||
|     return result; |     return parsed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_emv_parser_get_country_name(uint16_t country_code, string_t country_name) { | bool nfc_emv_parser_get_country_name( | ||||||
|     bool result = false; |     Storage* storage, | ||||||
|  |     uint16_t country_code, | ||||||
|  |     string_t country_name) { | ||||||
|  |     bool parsed = false; | ||||||
|     string_t key; |     string_t key; | ||||||
|     string_init_printf(key, "%04X", country_code); |     string_init_printf(key, "%04X", country_code); | ||||||
|     result = nfc_emv_parser_get_value("/ext/nfc/emv/country_code.nfc", key, ' ', country_name); |     if(nfc_emv_parser_search_data(storage, "/ext/nfc/emv/country_code.nfc", key, country_name)) { | ||||||
|  |         parsed = true; | ||||||
|  |     } | ||||||
|     string_clear(key); |     string_clear(key); | ||||||
|     return result; |     return parsed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_emv_parser_get_currency_name(uint16_t currency_code, string_t currency_name) { | bool nfc_emv_parser_get_currency_name( | ||||||
|     bool result = false; |     Storage* storage, | ||||||
|  |     uint16_t currency_code, | ||||||
|  |     string_t currency_name) { | ||||||
|  |     bool parsed = false; | ||||||
|     string_t key; |     string_t key; | ||||||
|     string_init_printf(key, "%04X", currency_code); |     string_init_printf(key, "%04X", currency_code); | ||||||
|     result = nfc_emv_parser_get_value("/ext/nfc/emv/currency_code.nfc", key, ' ', currency_name); |     if(nfc_emv_parser_search_data(storage, "/ext/nfc/emv/currency_code.nfc", key, currency_name)) { | ||||||
|     string_clear(key); |         parsed = true; | ||||||
|     return result; |     } | ||||||
|  |     string_clear(key); | ||||||
|  |     return parsed; | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,25 +3,39 @@ | |||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
|  | #include <storage/storage.h> | ||||||
| 
 | 
 | ||||||
| /** Get EMV application name by number
 | /** Get EMV application name by number
 | ||||||
|  |  * @param storage Storage instance | ||||||
|  * @param aid - AID number array |  * @param aid - AID number array | ||||||
|  * @param aid_len - AID length |  * @param aid_len - AID length | ||||||
|  * @param aid_name - string to keep AID name |  * @param aid_name - string to keep AID name | ||||||
|  * @return - true if AID found, false otherwies |  * @return - true if AID found, false otherwies | ||||||
|  */ |  */ | ||||||
| bool nfc_emv_parser_get_aid_name(uint8_t* aid, uint8_t aid_len, string_t aid_name); | bool nfc_emv_parser_get_aid_name( | ||||||
|  |     Storage* storage, | ||||||
|  |     uint8_t* aid, | ||||||
|  |     uint8_t aid_len, | ||||||
|  |     string_t aid_name); | ||||||
| 
 | 
 | ||||||
| /** Get country name by country code
 | /** Get country name by country code
 | ||||||
|  |  * @param storage Storage instance | ||||||
|  * @param country_code - ISO 3166 country code |  * @param country_code - ISO 3166 country code | ||||||
|  * @param country_name - string to keep country name |  * @param country_name - string to keep country name | ||||||
|  * @return - true if country found, false otherwies |  * @return - true if country found, false otherwies | ||||||
|  */ |  */ | ||||||
| bool nfc_emv_parser_get_country_name(uint16_t country_code, string_t country_name); | bool nfc_emv_parser_get_country_name( | ||||||
|  |     Storage* storage, | ||||||
|  |     uint16_t country_code, | ||||||
|  |     string_t country_name); | ||||||
| 
 | 
 | ||||||
| /** Get currency name by currency code
 | /** Get currency name by currency code
 | ||||||
|  |  * @param storage Storage instance | ||||||
|  * @param currency_code - ISO 3166 currency code |  * @param currency_code - ISO 3166 currency code | ||||||
|  * @param currency_name - string to keep currency name |  * @param currency_name - string to keep currency name | ||||||
|  * @return - true if currency found, false otherwies |  * @return - true if currency found, false otherwies | ||||||
|  */ |  */ | ||||||
| bool nfc_emv_parser_get_currency_name(uint16_t currency_code, string_t currency_name); | bool nfc_emv_parser_get_currency_name( | ||||||
|  |     Storage* storage, | ||||||
|  |     uint16_t currency_code, | ||||||
|  |     string_t currency_name); | ||||||
|  | |||||||
| @ -31,6 +31,9 @@ Nfc* nfc_alloc() { | |||||||
|     view_dispatcher_set_navigation_event_callback(nfc->view_dispatcher, nfc_back_event_callback); |     view_dispatcher_set_navigation_event_callback(nfc->view_dispatcher, nfc_back_event_callback); | ||||||
|     view_dispatcher_set_tick_event_callback(nfc->view_dispatcher, nfc_tick_event_callback, 100); |     view_dispatcher_set_tick_event_callback(nfc->view_dispatcher, nfc_tick_event_callback, 100); | ||||||
| 
 | 
 | ||||||
|  |     // Nfc device
 | ||||||
|  |     nfc->dev = nfc_device_alloc(); | ||||||
|  | 
 | ||||||
|     // Open GUI record
 |     // Open GUI record
 | ||||||
|     nfc->gui = furi_record_open("gui"); |     nfc->gui = furi_record_open("gui"); | ||||||
|     view_dispatcher_attach_to_gui(nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); |     view_dispatcher_attach_to_gui(nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); | ||||||
| @ -82,6 +85,9 @@ Nfc* nfc_alloc() { | |||||||
| void nfc_free(Nfc* nfc) { | void nfc_free(Nfc* nfc) { | ||||||
|     furi_assert(nfc); |     furi_assert(nfc); | ||||||
| 
 | 
 | ||||||
|  |     // Nfc device
 | ||||||
|  |     nfc_device_free(nfc->dev); | ||||||
|  | 
 | ||||||
|     // Submenu
 |     // Submenu
 | ||||||
|     view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewMenu); |     view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewMenu); | ||||||
|     submenu_free(nfc->submenu); |     submenu_free(nfc->submenu); | ||||||
| @ -154,8 +160,12 @@ int32_t nfc_app(void* p) { | |||||||
|     char* args = p; |     char* args = p; | ||||||
| 
 | 
 | ||||||
|     // Check argument and run corresponding scene
 |     // Check argument and run corresponding scene
 | ||||||
|     if((*args != '\0') && nfc_device_load(&nfc->dev, p)) { |     if((*args != '\0') && nfc_device_load(nfc->dev, p)) { | ||||||
|  |         if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl); | ||||||
|  |         } else { | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); | ||||||
|  |         } | ||||||
|     } else { |     } else { | ||||||
|         scene_manager_next_scene(nfc->scene_manager, NfcSceneStart); |         scene_manager_next_scene(nfc->scene_manager, NfcSceneStart); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,45 +1,38 @@ | |||||||
| #include "nfc_device_i.h" | #include "nfc_device.h" | ||||||
| 
 | 
 | ||||||
| #include <file-worker.h> |  | ||||||
| #include <lib/toolbox/path.h> | #include <lib/toolbox/path.h> | ||||||
| #include <lib/toolbox/hex.h> | #include <lib/flipper_file/flipper_file.h> | ||||||
| 
 |  | ||||||
| #define NFC_DEVICE_MAX_DATA_LEN 14 |  | ||||||
| 
 | 
 | ||||||
| static const char* nfc_app_folder = "/any/nfc"; | static const char* nfc_app_folder = "/any/nfc"; | ||||||
| static const char* nfc_app_extension = ".nfc"; | static const char* nfc_app_extension = ".nfc"; | ||||||
| static const char* nfc_app_shadow_extension = ".shd"; | static const char* nfc_app_shadow_extension = ".shd"; | ||||||
|  | static const char* nfc_file_header = "Flipper NFC device"; | ||||||
|  | static const uint32_t nfc_file_version = 2; | ||||||
| 
 | 
 | ||||||
| static bool nfc_device_read_hex(string_t str, uint8_t* buff, uint16_t len, uint8_t delim_len) { | NfcDevice* nfc_device_alloc() { | ||||||
|     string_strim(str); |     NfcDevice* nfc_dev = furi_alloc(sizeof(NfcDevice)); | ||||||
|     uint8_t nibble_high = 0; |     nfc_dev->storage = furi_record_open("storage"); | ||||||
|     uint8_t nibble_low = 0; |     nfc_dev->dialogs = furi_record_open("dialogs"); | ||||||
|     bool parsed = true; |     return nfc_dev; | ||||||
| 
 |  | ||||||
|     for(uint16_t i = 0; i < len; i++) { |  | ||||||
|         if(hex_char_to_hex_nibble(string_get_char(str, 0), &nibble_high) && |  | ||||||
|            hex_char_to_hex_nibble(string_get_char(str, 1), &nibble_low)) { |  | ||||||
|             buff[i] = (nibble_high << 4) | nibble_low; |  | ||||||
|             string_right(str, delim_len + 2); |  | ||||||
|         } else { |  | ||||||
|             parsed = false; |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return parsed; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| uint16_t nfc_device_prepare_format_string(NfcDevice* dev, string_t format_string) { | void nfc_device_free(NfcDevice* nfc_dev) { | ||||||
|  |     furi_assert(nfc_dev); | ||||||
|  |     furi_record_close("storage"); | ||||||
|  |     furi_record_close("dialogs"); | ||||||
|  |     free(nfc_dev); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_device_prepare_format_string(NfcDevice* dev, string_t format_string) { | ||||||
|     if(dev->format == NfcDeviceSaveFormatUid) { |     if(dev->format == NfcDeviceSaveFormatUid) { | ||||||
|         string_set_str(format_string, "UID\n"); |         string_set_str(format_string, "UID"); | ||||||
|     } else if(dev->format == NfcDeviceSaveFormatBankCard) { |     } else if(dev->format == NfcDeviceSaveFormatBankCard) { | ||||||
|         string_set_str(format_string, "Bank card\n"); |         string_set_str(format_string, "Bank card"); | ||||||
|     } else if(dev->format == NfcDeviceSaveFormatMifareUl) { |     } else if(dev->format == NfcDeviceSaveFormatMifareUl) { | ||||||
|         string_set_str(format_string, "Mifare Ultralight\n"); |         string_set_str(format_string, "Mifare Ultralight"); | ||||||
|     } else { |     } else { | ||||||
|         string_set_str(format_string, "Unknown\n"); |         string_set_str(format_string, "Unknown"); | ||||||
|     } |     } | ||||||
|     return string_size(format_string); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string) { | bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string) { | ||||||
| @ -59,228 +52,166 @@ bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string) { | |||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| uint16_t nfc_device_prepare_uid_string(NfcDevice* dev, string_t uid_string) { | static bool nfc_device_save_mifare_ul_data(FlipperFile* file, NfcDevice* dev) { | ||||||
|     NfcDeviceCommonData* uid_data = &dev->dev_data.nfc_data; |     bool saved = false; | ||||||
|     string_printf(uid_string, "UID len: %02X UID: ", dev->dev_data.nfc_data.uid_len); |  | ||||||
|     for(uint8_t i = 0; i < uid_data->uid_len; i++) { |  | ||||||
|         string_cat_printf(uid_string, "%02X ", uid_data->uid[i]); |  | ||||||
|     } |  | ||||||
|     string_cat_printf( |  | ||||||
|         uid_string, |  | ||||||
|         "ATQA: %02X %02X SAK: %02X\n", |  | ||||||
|         uid_data->atqa[0], |  | ||||||
|         uid_data->atqa[1], |  | ||||||
|         uid_data->sak); |  | ||||||
|     return string_size(uid_string); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool nfc_device_parse_uid_string(NfcDevice* dev, string_t uid_string) { |  | ||||||
|     NfcDeviceCommonData* uid_data = &dev->dev_data.nfc_data; |  | ||||||
|     bool parsed = false; |  | ||||||
| 
 |  | ||||||
|     do { |  | ||||||
|         // strlen("UID len: ") = 9
 |  | ||||||
|         string_right(uid_string, 9); |  | ||||||
|         if(!nfc_device_read_hex(uid_string, &uid_data->uid_len, 1, 1)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         // strlen("UID: ") = 5
 |  | ||||||
|         string_right(uid_string, 5); |  | ||||||
|         if(!nfc_device_read_hex(uid_string, uid_data->uid, uid_data->uid_len, 1)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         // strlen("ATQA: ") = 6
 |  | ||||||
|         string_right(uid_string, 6); |  | ||||||
|         if(!nfc_device_read_hex(uid_string, uid_data->atqa, 2, 1)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         // strlen("SAK: ") = 5
 |  | ||||||
|         string_right(uid_string, 5); |  | ||||||
|         if(!nfc_device_read_hex(uid_string, &uid_data->sak, 1, 1)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         parsed = true; |  | ||||||
|     } while(0); |  | ||||||
| 
 |  | ||||||
|     return parsed; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| uint16_t nfc_device_prepare_mifare_ul_string(NfcDevice* dev, string_t mifare_ul_string) { |  | ||||||
|     MifareUlData* data = &dev->dev_data.mf_ul_data; |     MifareUlData* data = &dev->dev_data.mf_ul_data; | ||||||
|     string_printf(mifare_ul_string, "Signature:"); |     string_t temp_str; | ||||||
|     for(uint8_t i = 0; i < sizeof(data->signature); i++) { |     string_init(temp_str); | ||||||
|         string_cat_printf(mifare_ul_string, " %02X", data->signature[i]); | 
 | ||||||
|     } |     // Save Mifare Ultralight specific data
 | ||||||
|     string_cat_printf(mifare_ul_string, "\nVersion:"); |     do { | ||||||
|     uint8_t* version = (uint8_t*)&data->version; |         if(!flipper_file_write_comment_cstr(file, "Mifare Ultralight specific data")) break; | ||||||
|     for(uint8_t i = 0; i < sizeof(data->version); i++) { |         if(!flipper_file_write_hex(file, "Signature", data->signature, sizeof(data->signature))) | ||||||
|         string_cat_printf(mifare_ul_string, " %02X", version[i]); |             break; | ||||||
|     } |         if(!flipper_file_write_hex( | ||||||
|  |                file, "Mifare version", (uint8_t*)&data->version, sizeof(data->version))) | ||||||
|  |             break; | ||||||
|  |         // Write conters and tearing flags data
 | ||||||
|  |         bool counters_saved = true; | ||||||
|         for(uint8_t i = 0; i < 3; i++) { |         for(uint8_t i = 0; i < 3; i++) { | ||||||
|         string_cat_printf( |             string_printf(temp_str, "Counter %d", i); | ||||||
|             mifare_ul_string, |             if(!flipper_file_write_uint32(file, string_get_cstr(temp_str), &data->counter[i], 1)) { | ||||||
|             "\nCounter %d: %lu Tearing flag %d: %02X", |                 counters_saved = false; | ||||||
|             i, |                 break; | ||||||
|             data->counter[i], |  | ||||||
|             i, |  | ||||||
|             data->tearing[i]); |  | ||||||
|             } |             } | ||||||
|     string_cat_printf(mifare_ul_string, "\nData size: %d\n", data->data_size); |             string_printf(temp_str, "Tearing %d", i); | ||||||
|  |             if(!flipper_file_write_hex(file, string_get_cstr(temp_str), &data->tearing[i], 1)) { | ||||||
|  |                 counters_saved = false; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if(!counters_saved) break; | ||||||
|  |         // Write pages data
 | ||||||
|  |         uint32_t pages_total = data->data_size / 4; | ||||||
|  |         if(!flipper_file_write_uint32(file, "Pages total", &pages_total, 1)) break; | ||||||
|  |         bool pages_saved = true; | ||||||
|         for(uint16_t i = 0; i < data->data_size; i += 4) { |         for(uint16_t i = 0; i < data->data_size; i += 4) { | ||||||
|         string_cat_printf( |             string_printf(temp_str, "Page %d", i / 4); | ||||||
|             mifare_ul_string, |             if(!flipper_file_write_hex(file, string_get_cstr(temp_str), &data->data[i], 4)) { | ||||||
|             "%02X %02X %02X %02X\n", |                 pages_saved = false; | ||||||
|             data->data[i], |                 break; | ||||||
|             data->data[i + 1], |  | ||||||
|             data->data[i + 2], |  | ||||||
|             data->data[i + 3]); |  | ||||||
|             } |             } | ||||||
|     return string_size(mifare_ul_string); |         } | ||||||
|  |         if(!pages_saved) break; | ||||||
|  |         saved = true; | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     string_clear(temp_str); | ||||||
|  |     return saved; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_parse_mifare_ul_string(NfcDevice* dev, string_t mifare_ul_string) { | bool nfc_device_load_mifare_ul_data(FlipperFile* file, NfcDevice* dev) { | ||||||
|     MifareUlData* data = &dev->dev_data.mf_ul_data; |  | ||||||
|     uint16_t tearing_tmp = 0; |  | ||||||
|     uint16_t cnt_num = 0; |  | ||||||
|     size_t ws = 0; |  | ||||||
|     int res = 0; |  | ||||||
|     bool parsed = false; |     bool parsed = false; | ||||||
|  |     MifareUlData* data = &dev->dev_data.mf_ul_data; | ||||||
|  |     string_t temp_str; | ||||||
|  |     string_init(temp_str); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         // strlen("Signature: ") = 11
 |         // Read signature
 | ||||||
|         string_right(mifare_ul_string, 11); |         if(!flipper_file_read_hex(file, "Signature", data->signature, sizeof(data->signature))) | ||||||
|         if(!nfc_device_read_hex(mifare_ul_string, data->signature, sizeof(data->signature), 1)) { |  | ||||||
|             break; |             break; | ||||||
|         } |         // Read Mifare version
 | ||||||
|         // strlen("Version: ") = 9
 |         if(!flipper_file_read_hex( | ||||||
|         string_right(mifare_ul_string, 9); |                file, "Mifare version", (uint8_t*)&data->version, sizeof(data->version))) | ||||||
|         if(!nfc_device_read_hex( |  | ||||||
|                mifare_ul_string, (uint8_t*)&data->version, sizeof(data->version), 1)) { |  | ||||||
|             break; |             break; | ||||||
|         } |  | ||||||
|         string_strim(mifare_ul_string); |  | ||||||
|         // Read counters and tearing flags
 |         // Read counters and tearing flags
 | ||||||
|  |         bool counters_parsed = true; | ||||||
|         for(uint8_t i = 0; i < 3; i++) { |         for(uint8_t i = 0; i < 3; i++) { | ||||||
|             res = sscanf( |             string_printf(temp_str, "Counter %d", i); | ||||||
|                 string_get_cstr(mifare_ul_string), |             if(!flipper_file_read_uint32(file, string_get_cstr(temp_str), &data->counter[i], 1)) { | ||||||
|                 "Counter %hX: %lu Tearing flag %hX: %02hX", |                 counters_parsed = false; | ||||||
|                 &cnt_num, |  | ||||||
|                 &data->counter[i], |  | ||||||
|                 &cnt_num, |  | ||||||
|                 &tearing_tmp); |  | ||||||
|             if(res != 4) { |  | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             data->tearing[i] = tearing_tmp; |             string_printf(temp_str, "Tearing %d", i); | ||||||
|             ws = string_search_char(mifare_ul_string, '\n'); |             if(!flipper_file_read_hex(file, string_get_cstr(temp_str), &data->tearing[i], 1)) { | ||||||
|             string_right(mifare_ul_string, ws + 1); |                 counters_parsed = false; | ||||||
|         } |  | ||||||
|         // Read data size
 |  | ||||||
|         res = sscanf(string_get_cstr(mifare_ul_string), "Data size: %hu", &data->data_size); |  | ||||||
|         if(res != 1) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         ws = string_search_char(mifare_ul_string, '\n'); |  | ||||||
|         string_right(mifare_ul_string, ws + 1); |  | ||||||
|         // Read data
 |  | ||||||
|         for(uint16_t i = 0; i < data->data_size; i += 4) { |  | ||||||
|             if(!nfc_device_read_hex(mifare_ul_string, &data->data[i], 4, 1)) { |  | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         if(!counters_parsed) break; | ||||||
|  |         // Read pages
 | ||||||
|  |         uint32_t pages = 0; | ||||||
|  |         if(!flipper_file_read_uint32(file, "Pages total", &pages, 1)) break; | ||||||
|  |         data->data_size = pages * 4; | ||||||
|  |         bool pages_parsed = true; | ||||||
|  |         for(uint16_t i = 0; i < pages; i++) { | ||||||
|  |             string_printf(temp_str, "Page %d", i); | ||||||
|  |             if(!flipper_file_read_hex(file, string_get_cstr(temp_str), &data->data[i * 4], 4)) { | ||||||
|  |                 pages_parsed = false; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if(!pages_parsed) break; | ||||||
|         parsed = true; |         parsed = true; | ||||||
|     } while(0); |     } while(false); | ||||||
| 
 | 
 | ||||||
|  |     string_clear(temp_str); | ||||||
|     return parsed; |     return parsed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| uint16_t nfc_device_prepare_bank_card_string(NfcDevice* dev, string_t bank_card_string) { | static bool nfc_device_save_bank_card_data(FlipperFile* file, NfcDevice* dev) { | ||||||
|  |     bool saved = false; | ||||||
|     NfcEmvData* data = &dev->dev_data.emv_data; |     NfcEmvData* data = &dev->dev_data.emv_data; | ||||||
|     string_printf(bank_card_string, "AID len: %d, AID:", data->aid_len); |     uint32_t data_temp = 0; | ||||||
|     for(uint8_t i = 0; i < data->aid_len; i++) { | 
 | ||||||
|         string_cat_printf(bank_card_string, " %02X", data->aid[i]); |     do { | ||||||
|     } |         // Write Bank card specific data
 | ||||||
|     string_cat_printf( |         if(!flipper_file_write_comment_cstr(file, "Bank card specific data")) break; | ||||||
|         bank_card_string, "\nName: %s\nNumber len: %d\nNumber:", data->name, data->number_len); |         if(!flipper_file_write_hex(file, "AID", data->aid, data->aid_len)) break; | ||||||
|     for(uint8_t i = 0; i < data->number_len; i++) { |         if(!flipper_file_write_string_cstr(file, "Name", data->name)) break; | ||||||
|         string_cat_printf(bank_card_string, " %02X", data->number[i]); |         if(!flipper_file_write_hex(file, "Number", data->number, data->number_len)) break; | ||||||
|     } |  | ||||||
|         if(data->exp_mon) { |         if(data->exp_mon) { | ||||||
|         string_cat_printf( |             uint8_t exp_data[2] = {data->exp_mon, data->exp_year}; | ||||||
|             bank_card_string, "\nExp date: %02X/%02X", data->exp_mon, data->exp_year); |             if(!flipper_file_write_hex(file, "Exp data", exp_data, sizeof(exp_data))) break; | ||||||
|         } |         } | ||||||
|         if(data->country_code) { |         if(data->country_code) { | ||||||
|         string_cat_printf(bank_card_string, "\nCountry code: %04X", data->country_code); |             data_temp = data->country_code; | ||||||
|  |             if(!flipper_file_write_uint32(file, "Country code", &data_temp, 1)) break; | ||||||
|         } |         } | ||||||
|         if(data->currency_code) { |         if(data->currency_code) { | ||||||
|         string_cat_printf(bank_card_string, "\nCurrency code: %04X", data->currency_code); |             data_temp = data->currency_code; | ||||||
|  |             if(!flipper_file_write_uint32(file, "Currency code", &data_temp, 1)) break; | ||||||
|         } |         } | ||||||
|     return string_size(bank_card_string); |         saved = true; | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     return saved; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_parse_bank_card_string(NfcDevice* dev, string_t bank_card_string) { | bool nfc_device_load_bank_card_data(FlipperFile* file, NfcDevice* dev) { | ||||||
|     NfcEmvData* data = &dev->dev_data.emv_data; |  | ||||||
|     bool parsed = false; |     bool parsed = false; | ||||||
|     int res = 0; |     NfcEmvData* data = &dev->dev_data.emv_data; | ||||||
|     uint8_t code[2] = {}; |  | ||||||
|     memset(data, 0, sizeof(NfcEmvData)); |     memset(data, 0, sizeof(NfcEmvData)); | ||||||
|  |     uint32_t data_cnt = 0; | ||||||
|  |     string_t temp_str; | ||||||
|  |     string_init(temp_str); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         res = sscanf(string_get_cstr(bank_card_string), "AID len: %hu", &data->aid_len); |         // Load essential data
 | ||||||
|         if(res != 1) { |         if(!flipper_file_get_value_count(file, "AID", &data_cnt)) break; | ||||||
|             break; |         data->aid_len = data_cnt; | ||||||
|         } |         if(!flipper_file_read_hex(file, "AID", data->aid, data->aid_len)) break; | ||||||
|         // strlen("AID len: ") = 9
 |         if(!flipper_file_read_string(file, "Name", temp_str)) break; | ||||||
|         string_right(bank_card_string, 9); |         strlcpy(data->name, string_get_cstr(temp_str), sizeof(data->name)); | ||||||
|         size_t ws = string_search_char(bank_card_string, ':'); |         if(!flipper_file_get_value_count(file, "Number", &data_cnt)) break; | ||||||
|         string_right(bank_card_string, ws + 1); |         data->number_len = data_cnt; | ||||||
|         if(!nfc_device_read_hex(bank_card_string, data->aid, data->aid_len, 1)) { |         if(!flipper_file_read_hex(file, "Number", data->number, data->number_len)) break; | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         res = sscanf(string_get_cstr(bank_card_string), "Name: %s\n", data->name); |  | ||||||
|         if(res != 1) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         ws = string_search_char(bank_card_string, '\n'); |  | ||||||
|         string_right(bank_card_string, ws + 1); |  | ||||||
|         res = sscanf(string_get_cstr(bank_card_string), "Number len: %hhu", &data->number_len); |  | ||||||
|         if(res != 1) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         ws = string_search_char(bank_card_string, '\n'); |  | ||||||
|         string_right(bank_card_string, ws + 1); |  | ||||||
|         // strlen("Number: ") = 8
 |  | ||||||
|         string_right(bank_card_string, 8); |  | ||||||
|         if(!nfc_device_read_hex(bank_card_string, data->number, data->number_len, 1)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         parsed = true; |         parsed = true; | ||||||
|         // Check expiration date presence
 |         // Load optional data
 | ||||||
|         ws = string_search_str(bank_card_string, "Exp date: "); |         uint8_t exp_data[2] = {}; | ||||||
|         if(ws != STRING_FAILURE) { |         if(flipper_file_read_hex(file, "Exp data", exp_data, 2)) { | ||||||
|             // strlen("Exp date: ") = 10
 |             data->exp_mon = exp_data[0]; | ||||||
|             string_right(bank_card_string, 10); |             data->exp_year = exp_data[1]; | ||||||
|             nfc_device_read_hex(bank_card_string, &data->exp_mon, 1, 1); |  | ||||||
|             nfc_device_read_hex(bank_card_string, &data->exp_year, 1, 1); |  | ||||||
|         } |         } | ||||||
|         // Check country code presence
 |         if(flipper_file_read_uint32(file, "Country code", &data_cnt, 1)) { | ||||||
|         ws = string_search_str(bank_card_string, "Country code: "); |             data->country_code = data_cnt; | ||||||
|         if(ws != STRING_FAILURE) { |  | ||||||
|             // strlen("Country code: ") = 14
 |  | ||||||
|             string_right(bank_card_string, 14); |  | ||||||
|             nfc_device_read_hex(bank_card_string, code, 2, 0); |  | ||||||
|             data->country_code = code[0] << 8 | code[1]; |  | ||||||
|         } |         } | ||||||
|         // Check currency code presence
 |         if(flipper_file_read_uint32(file, "Currency code", &data_cnt, 1)) { | ||||||
|         ws = string_search_str(bank_card_string, "Currency code: "); |             data->currency_code = data_cnt; | ||||||
|         if(ws != STRING_FAILURE) { |  | ||||||
|             // strlen("Currency code: ") = 15
 |  | ||||||
|             string_right(bank_card_string, 15); |  | ||||||
|             nfc_device_read_hex(bank_card_string, code, 2, 0); |  | ||||||
|             data->currency_code = code[0] << 8 | code[1]; |  | ||||||
|         } |         } | ||||||
|     } while(0); |     } while(false); | ||||||
| 
 | 
 | ||||||
|  |     string_clear(temp_str); | ||||||
|     return parsed; |     return parsed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -297,58 +228,49 @@ static bool nfc_device_save_file( | |||||||
|     const char* extension) { |     const char* extension) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
| 
 | 
 | ||||||
|     FileWorker* file_worker = file_worker_alloc(false); |     bool saved = false; | ||||||
|     string_t dev_file_name; |     FlipperFile* file = flipper_file_alloc(dev->storage); | ||||||
|     string_init(dev_file_name); |     NfcDeviceCommonData* data = &dev->dev_data.nfc_data; | ||||||
|     string_t temp_str; |     string_t temp_str; | ||||||
|     string_init(temp_str); |     string_init(temp_str); | ||||||
|     uint16_t string_len = 0; |  | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         // Create nfc directory if necessary
 |         // Create nfc directory if necessary
 | ||||||
|         if(!file_worker_mkdir(file_worker, nfc_app_folder)) { |         if(!storage_simply_mkdir(dev->storage, nfc_app_folder)) break; | ||||||
|             break; |  | ||||||
|         }; |  | ||||||
|         // First remove nfc device file if it was saved
 |         // First remove nfc device file if it was saved
 | ||||||
|         string_printf(dev_file_name, "%s/%s%s", folder, dev_name, extension); |         string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); | ||||||
|         if(!file_worker_remove(file_worker, string_get_cstr(dev_file_name))) { |  | ||||||
|             break; |  | ||||||
|         }; |  | ||||||
|         // Open file
 |         // Open file
 | ||||||
|         if(!file_worker_open( |         if(!flipper_file_open_always(file, string_get_cstr(temp_str))) break; | ||||||
|                file_worker, string_get_cstr(dev_file_name), FSAM_WRITE, FSOM_CREATE_ALWAYS)) { |         // Write header
 | ||||||
|  |         if(!flipper_file_write_header_cstr(file, nfc_file_header, nfc_file_version)) break; | ||||||
|  |         // Write nfc device type
 | ||||||
|  |         if(!flipper_file_write_comment_cstr( | ||||||
|  |                file, "Nfc device type can be UID, Mifare Ultralight, Bank card")) | ||||||
|             break; |             break; | ||||||
|         } |         nfc_device_prepare_format_string(dev, temp_str); | ||||||
|         // Prepare and write format name on 1st line
 |         if(!flipper_file_write_string(file, "Device type", temp_str)) break; | ||||||
|         string_len = nfc_device_prepare_format_string(dev, temp_str); |         // Write UID, ATQA, SAK
 | ||||||
|         if(!file_worker_write(file_worker, string_get_cstr(temp_str), string_len)) { |         if(!flipper_file_write_comment_cstr(file, "UID, ATQA and SAK are common for all formats")) | ||||||
|             break; |             break; | ||||||
|         } |         if(!flipper_file_write_hex(file, "UID", data->uid, data->uid_len)) break; | ||||||
|         // Prepare and write UID data on 2nd line
 |         if(!flipper_file_write_hex(file, "ATQA", data->atqa, 2)) break; | ||||||
|         string_len = nfc_device_prepare_uid_string(dev, temp_str); |         if(!flipper_file_write_hex(file, "SAK", &data->sak, 1)) break; | ||||||
|         if(!file_worker_write(file_worker, string_get_cstr(temp_str), string_len)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         // Save more data if necessary
 |         // Save more data if necessary
 | ||||||
|         if(dev->format == NfcDeviceSaveFormatMifareUl) { |         if(dev->format == NfcDeviceSaveFormatMifareUl) { | ||||||
|             string_len = nfc_device_prepare_mifare_ul_string(dev, temp_str); |             if(!nfc_device_save_mifare_ul_data(file, dev)) break; | ||||||
|             if(!file_worker_write(file_worker, string_get_cstr(temp_str), string_len)) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } else if(dev->format == NfcDeviceSaveFormatBankCard) { |         } else if(dev->format == NfcDeviceSaveFormatBankCard) { | ||||||
|             string_len = nfc_device_prepare_bank_card_string(dev, temp_str); |             if(!nfc_device_save_bank_card_data(file, dev)) break; | ||||||
|             if(!file_worker_write(file_worker, string_get_cstr(temp_str), string_len)) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |         saved = true; | ||||||
|     } while(0); |     } while(0); | ||||||
| 
 | 
 | ||||||
|  |     if(!saved) { | ||||||
|  |         dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); | ||||||
|  |     } | ||||||
|     string_clear(temp_str); |     string_clear(temp_str); | ||||||
|     string_clear(dev_file_name); |     flipper_file_close(file); | ||||||
|     file_worker_close(file_worker); |     flipper_file_free(file); | ||||||
|     file_worker_free(file_worker); |     return saved; | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_save(NfcDevice* dev, const char* dev_name) { | bool nfc_device_save(NfcDevice* dev, const char* dev_name) { | ||||||
| @ -360,73 +282,64 @@ bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) { | |||||||
|     return nfc_device_save_file(dev, dev_name, nfc_app_folder, nfc_app_shadow_extension); |     return nfc_device_save_file(dev, dev_name, nfc_app_folder, nfc_app_shadow_extension); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool nfc_device_load_data(FileWorker* file_worker, string_t path, NfcDevice* dev) { | static bool nfc_device_load_data(NfcDevice* dev, string_t path) { | ||||||
|     string_t temp_string; |  | ||||||
|     string_init(temp_string); |  | ||||||
|     bool parsed = false; |     bool parsed = false; | ||||||
|  |     FlipperFile* file = flipper_file_alloc(dev->storage); | ||||||
|  |     NfcDeviceCommonData* data = &dev->dev_data.nfc_data; | ||||||
|  |     uint32_t data_cnt = 0; | ||||||
|  |     string_t temp_str; | ||||||
|  |     string_init(temp_str); | ||||||
|  |     bool depricated_version = false; | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         // Check existance of shadow file
 |         // Check existance of shadow file
 | ||||||
|         size_t ext_start = string_search_str(path, nfc_app_extension); |         size_t ext_start = string_search_str(path, nfc_app_extension); | ||||||
|         string_set_n(temp_string, path, 0, ext_start); |         string_set_n(temp_str, path, 0, ext_start); | ||||||
|         string_cat_printf(temp_string, "%s", nfc_app_shadow_extension); |         string_cat_printf(temp_str, "%s", nfc_app_shadow_extension); | ||||||
|         if(!file_worker_is_file_exist( |         dev->shadow_file_exist = | ||||||
|                file_worker, string_get_cstr(temp_string), &dev->shadow_file_exist)) { |             storage_common_stat(dev->storage, string_get_cstr(temp_str), NULL) == FSE_OK; | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         // Open shadow file if it exists. If not - open original
 |         // Open shadow file if it exists. If not - open original
 | ||||||
|         if(dev->shadow_file_exist) { |         if(dev->shadow_file_exist) { | ||||||
|             if(!file_worker_open( |             if(!flipper_file_open_existing(file, string_get_cstr(temp_str))) break; | ||||||
|                    file_worker, string_get_cstr(temp_string), FSAM_READ, FSOM_OPEN_EXISTING)) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } else { |         } else { | ||||||
|             if(!file_worker_open( |             if(!flipper_file_open_existing(file, string_get_cstr(path))) break; | ||||||
|                    file_worker, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { |         } | ||||||
|                 break; |         // Read and verify file header
 | ||||||
|             } |         uint32_t version = 0; | ||||||
|         } |         if(!flipper_file_read_header(file, temp_str, &version)) break; | ||||||
| 
 |         if(string_cmp_str(temp_str, nfc_file_header) || (version != nfc_file_version)) { | ||||||
|         // Read and parse format from 1st line
 |             depricated_version = true; | ||||||
|         if(!file_worker_read_until(file_worker, temp_string, '\n')) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         if(!nfc_device_parse_format_string(dev, temp_string)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         // Read and parse UID data from 2nd line
 |  | ||||||
|         if(!file_worker_read_until(file_worker, temp_string, '\n')) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         if(!nfc_device_parse_uid_string(dev, temp_string)) { |  | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  |         // Read Nfc device type
 | ||||||
|  |         if(!flipper_file_read_string(file, "Device type", temp_str)) break; | ||||||
|  |         if(!nfc_device_parse_format_string(dev, temp_str)) break; | ||||||
|  |         // Read and parse UID, ATQA and SAK
 | ||||||
|  |         if(!flipper_file_get_value_count(file, "UID", &data_cnt)) break; | ||||||
|  |         data->uid_len = data_cnt; | ||||||
|  |         if(!flipper_file_read_hex(file, "UID", data->uid, data->uid_len)) break; | ||||||
|  |         if(!flipper_file_read_hex(file, "ATQA", data->atqa, 2)) break; | ||||||
|  |         if(!flipper_file_read_hex(file, "SAK", &data->sak, 1)) break; | ||||||
|         // Parse other data
 |         // Parse other data
 | ||||||
|         if(dev->format == NfcDeviceSaveFormatMifareUl) { |         if(dev->format == NfcDeviceSaveFormatMifareUl) { | ||||||
|             // Read until EOF
 |             if(!nfc_device_load_mifare_ul_data(file, dev)) break; | ||||||
|             if(!file_worker_read_until(file_worker, temp_string, 0x05)) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             if(!nfc_device_parse_mifare_ul_string(dev, temp_string)) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } else if(dev->format == NfcDeviceSaveFormatBankCard) { |         } else if(dev->format == NfcDeviceSaveFormatBankCard) { | ||||||
|             // Read until EOF
 |             if(!nfc_device_load_bank_card_data(file, dev)) break; | ||||||
|             if(!file_worker_read_until(file_worker, temp_string, 0x05)) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             if(!nfc_device_parse_bank_card_string(dev, temp_string)) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|         parsed = true; |         parsed = true; | ||||||
|     } while(0); |     } while(false); | ||||||
| 
 | 
 | ||||||
|     if(!parsed) { |     if(!parsed) { | ||||||
|         file_worker_show_error(file_worker, "Can not parse\nfile"); |         if(depricated_version) { | ||||||
|  |             dialog_message_show_storage_error(dev->dialogs, "File format depricated"); | ||||||
|  |         } else { | ||||||
|  |             dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile"); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     string_clear(temp_string); |     string_clear(temp_str); | ||||||
|  |     flipper_file_close(file); | ||||||
|  |     flipper_file_free(file); | ||||||
|     return parsed; |     return parsed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -434,19 +347,16 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { | |||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
|     furi_assert(file_path); |     furi_assert(file_path); | ||||||
| 
 | 
 | ||||||
|     FileWorker* file_worker = file_worker_alloc(false); |  | ||||||
|     // Load device data
 |     // Load device data
 | ||||||
|     string_t path; |     string_t path; | ||||||
|     string_init_set_str(path, file_path); |     string_init_set_str(path, file_path); | ||||||
|     bool dev_load = nfc_device_load_data(file_worker, path, dev); |     bool dev_load = nfc_device_load_data(dev, path); | ||||||
|     if(dev_load) { |     if(dev_load) { | ||||||
|         // Set device name
 |         // Set device name
 | ||||||
|         path_extract_filename_no_ext(file_path, path); |         path_extract_filename_no_ext(file_path, path); | ||||||
|         nfc_device_set_name(dev, string_get_cstr(path)); |         nfc_device_set_name(dev, string_get_cstr(path)); | ||||||
|     } |     } | ||||||
|     string_clear(path); |     string_clear(path); | ||||||
|     file_worker_close(file_worker); |  | ||||||
|     file_worker_free(file_worker); |  | ||||||
| 
 | 
 | ||||||
|     return dev_load; |     return dev_load; | ||||||
| } | } | ||||||
| @ -454,10 +364,9 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { | |||||||
| bool nfc_file_select(NfcDevice* dev) { | bool nfc_file_select(NfcDevice* dev) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
| 
 | 
 | ||||||
|     FileWorker* file_worker = file_worker_alloc(false); |  | ||||||
|     // Input events and views are managed by file_select
 |     // Input events and views are managed by file_select
 | ||||||
|     bool res = file_worker_file_select( |     bool res = dialog_file_select_show( | ||||||
|         file_worker, |         dev->dialogs, | ||||||
|         nfc_app_folder, |         nfc_app_folder, | ||||||
|         nfc_app_extension, |         nfc_app_extension, | ||||||
|         dev->file_name, |         dev->file_name, | ||||||
| @ -465,18 +374,14 @@ bool nfc_file_select(NfcDevice* dev) { | |||||||
|         dev->dev_name); |         dev->dev_name); | ||||||
|     if(res) { |     if(res) { | ||||||
|         string_t dev_str; |         string_t dev_str; | ||||||
| 
 |  | ||||||
|         // Get key file path
 |         // Get key file path
 | ||||||
|         string_init_printf(dev_str, "%s/%s%s", nfc_app_folder, dev->file_name, nfc_app_extension); |         string_init_printf(dev_str, "%s/%s%s", nfc_app_folder, dev->file_name, nfc_app_extension); | ||||||
| 
 |         res = nfc_device_load_data(dev, dev_str); | ||||||
|         res = nfc_device_load_data(file_worker, dev_str, dev); |  | ||||||
|         if(res) { |         if(res) { | ||||||
|             nfc_device_set_name(dev, dev->file_name); |             nfc_device_set_name(dev, dev->file_name); | ||||||
|         } |         } | ||||||
|         string_clear(dev_str); |         string_clear(dev_str); | ||||||
|     } |     } | ||||||
|     file_worker_close(file_worker); |  | ||||||
|     file_worker_free(file_worker); |  | ||||||
| 
 | 
 | ||||||
|     return res; |     return res; | ||||||
| } | } | ||||||
| @ -491,61 +396,48 @@ void nfc_device_clear(NfcDevice* dev) { | |||||||
| bool nfc_device_delete(NfcDevice* dev) { | bool nfc_device_delete(NfcDevice* dev) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
| 
 | 
 | ||||||
|     bool result = true; |     bool deleted = false; | ||||||
|     FileWorker* file_worker = file_worker_alloc(false); |  | ||||||
|     string_t file_path; |     string_t file_path; | ||||||
|  |     string_init(file_path); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         // Delete original file
 |         // Delete original file
 | ||||||
|         string_init_printf(file_path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_extension); |         string_init_printf(file_path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_extension); | ||||||
|         if(!file_worker_remove(file_worker, string_get_cstr(file_path))) { |         if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; | ||||||
|             result = false; |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         // Delete shadow file if it exists
 |         // Delete shadow file if it exists
 | ||||||
|         if(dev->shadow_file_exist) { |         if(dev->shadow_file_exist) { | ||||||
|             string_clean(file_path); |  | ||||||
|             string_printf( |             string_printf( | ||||||
|                 file_path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_shadow_extension); |                 file_path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_shadow_extension); | ||||||
|             if(!file_worker_remove(file_worker, string_get_cstr(file_path))) { |             if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; | ||||||
|                 result = false; |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |         deleted = true; | ||||||
|     } while(0); |     } while(0); | ||||||
| 
 | 
 | ||||||
|  |     if(!deleted) { | ||||||
|  |         dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     string_clear(file_path); |     string_clear(file_path); | ||||||
|     file_worker_close(file_worker); |     return deleted; | ||||||
|     file_worker_free(file_worker); |  | ||||||
|     return result; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_restore(NfcDevice* dev) { | bool nfc_device_restore(NfcDevice* dev) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
|     furi_assert(dev->shadow_file_exist); |     furi_assert(dev->shadow_file_exist); | ||||||
| 
 | 
 | ||||||
|     bool result = true; |     bool restored = false; | ||||||
|     FileWorker* file_worker = file_worker_alloc(false); |  | ||||||
|     string_t path; |     string_t path; | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         string_init_printf( |         string_init_printf( | ||||||
|             path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_shadow_extension); |             path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_shadow_extension); | ||||||
|         if(!file_worker_remove(file_worker, string_get_cstr(path))) { |         if(!storage_simply_remove(dev->storage, string_get_cstr(path))) break; | ||||||
|             result = false; |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         dev->shadow_file_exist = false; |         dev->shadow_file_exist = false; | ||||||
|         string_clean(path); |  | ||||||
|         string_printf(path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_extension); |         string_printf(path, "%s/%s%s", nfc_app_folder, dev->dev_name, nfc_app_extension); | ||||||
|         if(!nfc_device_load_data(file_worker, path, dev)) { |         if(!nfc_device_load_data(dev, path)) break; | ||||||
|             result = false; |         restored = true; | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } while(0); |     } while(0); | ||||||
| 
 | 
 | ||||||
|     string_clear(path); |     string_clear(path); | ||||||
|     file_worker_close(file_worker); |     return restored; | ||||||
|     file_worker_free(file_worker); |  | ||||||
|     return result; |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ | |||||||
| 
 | 
 | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <dialogs/dialogs.h> | ||||||
| 
 | 
 | ||||||
| #include "mifare_ultralight.h" | #include "mifare_ultralight.h" | ||||||
| 
 | 
 | ||||||
| @ -57,6 +59,8 @@ typedef struct { | |||||||
| } NfcDeviceData; | } NfcDeviceData; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|  |     Storage* storage; | ||||||
|  |     DialogsApp* dialogs; | ||||||
|     NfcDeviceData dev_data; |     NfcDeviceData dev_data; | ||||||
|     char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; |     char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; | ||||||
|     char file_name[NFC_FILE_NAME_MAX_LEN]; |     char file_name[NFC_FILE_NAME_MAX_LEN]; | ||||||
| @ -64,6 +68,10 @@ typedef struct { | |||||||
|     bool shadow_file_exist; |     bool shadow_file_exist; | ||||||
| } NfcDevice; | } NfcDevice; | ||||||
| 
 | 
 | ||||||
|  | NfcDevice* nfc_device_alloc(); | ||||||
|  | 
 | ||||||
|  | void nfc_device_free(NfcDevice* nfc_dev); | ||||||
|  | 
 | ||||||
| void nfc_device_set_name(NfcDevice* dev, const char* name); | void nfc_device_set_name(NfcDevice* dev, const char* name); | ||||||
| 
 | 
 | ||||||
| bool nfc_device_save(NfcDevice* dev, const char* dev_name); | bool nfc_device_save(NfcDevice* dev, const char* dev_name); | ||||||
|  | |||||||
| @ -1,16 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include "nfc_device.h" |  | ||||||
| #include <m-string.h> |  | ||||||
| 
 |  | ||||||
| uint16_t nfc_device_prepare_format_string(NfcDevice* dev, string_t format_string); |  | ||||||
| bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string); |  | ||||||
| 
 |  | ||||||
| uint16_t nfc_device_prepare_uid_string(NfcDevice* dev, string_t uid_string); |  | ||||||
| bool nfc_device_parse_uid_string(NfcDevice* dev, string_t uid_string); |  | ||||||
| 
 |  | ||||||
| uint16_t nfc_device_prepare_mifare_ul_string(NfcDevice* dev, string_t mifare_ul_string); |  | ||||||
| bool nfc_device_parse_mifare_ul_string(NfcDevice* dev, string_t mifare_ul_string); |  | ||||||
| 
 |  | ||||||
| uint16_t nfc_device_prepare_bank_card_string(NfcDevice* dev, string_t bank_card_string); |  | ||||||
| bool nfc_device_parse_bank_card_string(NfcDevice* dev, string_t bank_card_string); |  | ||||||
| @ -36,7 +36,7 @@ struct Nfc { | |||||||
|     Gui* gui; |     Gui* gui; | ||||||
|     NotificationApp* notifications; |     NotificationApp* notifications; | ||||||
|     SceneManager* scene_manager; |     SceneManager* scene_manager; | ||||||
|     NfcDevice dev; |     NfcDevice* dev; | ||||||
|     NfcDeviceCommonData dev_edit_data; |     NfcDeviceCommonData dev_edit_data; | ||||||
| 
 | 
 | ||||||
|     char text_store[NFC_TEXT_STORE_SIZE + 1]; |     char text_store[NFC_TEXT_STORE_SIZE + 1]; | ||||||
|  | |||||||
							
								
								
									
										145
									
								
								applications/nfc/nfc_worker.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										145
									
								
								applications/nfc/nfc_worker.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -3,14 +3,14 @@ | |||||||
| #include "nfc_protocols/emv_decoder.h" | #include "nfc_protocols/emv_decoder.h" | ||||||
| #include "nfc_protocols/mifare_ultralight.h" | #include "nfc_protocols/mifare_ultralight.h" | ||||||
| 
 | 
 | ||||||
| #define NFC_WORKER_TAG "nfc worker" | #define TAG "NfcWorker" | ||||||
| 
 | 
 | ||||||
| /***************************** NFC Worker API *******************************/ | /***************************** NFC Worker API *******************************/ | ||||||
| 
 | 
 | ||||||
| NfcWorker* nfc_worker_alloc() { | NfcWorker* nfc_worker_alloc() { | ||||||
|     NfcWorker* nfc_worker = furi_alloc(sizeof(NfcWorker)); |     NfcWorker* nfc_worker = furi_alloc(sizeof(NfcWorker)); | ||||||
|     // Worker thread attributes
 |     // Worker thread attributes
 | ||||||
|     nfc_worker->thread_attr.name = "nfc_worker"; |     nfc_worker->thread_attr.name = "NfcWorker"; | ||||||
|     nfc_worker->thread_attr.stack_size = 8192; |     nfc_worker->thread_attr.stack_size = 8192; | ||||||
|     nfc_worker->callback = NULL; |     nfc_worker->callback = NULL; | ||||||
|     nfc_worker->context = NULL; |     nfc_worker->context = NULL; | ||||||
| @ -144,7 +144,7 @@ void nfc_worker_emulate(NfcWorker* nfc_worker) { | |||||||
|     NfcDeviceCommonData* data = &nfc_worker->dev_data->nfc_data; |     NfcDeviceCommonData* data = &nfc_worker->dev_data->nfc_data; | ||||||
|     while(nfc_worker->state == NfcWorkerStateEmulate) { |     while(nfc_worker->state == NfcWorkerStateEmulate) { | ||||||
|         if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, false, 100)) { |         if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, false, 100)) { | ||||||
|             FURI_LOG_I(NFC_WORKER_TAG, "Reader detected"); |             FURI_LOG_I(TAG, "Reader detected"); | ||||||
|         } |         } | ||||||
|         osDelay(10); |         osDelay(10); | ||||||
|     } |     } | ||||||
| @ -174,18 +174,17 @@ void nfc_worker_read_emv_app(NfcWorker* nfc_worker) { | |||||||
|                     result->nfc_data.uid, dev_list[0].dev.nfca.nfcId1, result->nfc_data.uid_len); |                     result->nfc_data.uid, dev_list[0].dev.nfca.nfcId1, result->nfc_data.uid_len); | ||||||
|                 result->nfc_data.protocol = NfcDeviceProtocolEMV; |                 result->nfc_data.protocol = NfcDeviceProtocolEMV; | ||||||
| 
 | 
 | ||||||
|                 FURI_LOG_I(NFC_WORKER_TAG, "Send select PPSE command"); |                 FURI_LOG_I(TAG, "Send select PPSE command"); | ||||||
|                 tx_len = emv_prepare_select_ppse(tx_buff); |                 tx_len = emv_prepare_select_ppse(tx_buff); | ||||||
|                 err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); |                 err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); | ||||||
|                 if(err != ERR_NONE) { |                 if(err != ERR_NONE) { | ||||||
|                     FURI_LOG_E(NFC_WORKER_TAG, "Error during selection PPSE request: %d", err); |                     FURI_LOG_E(TAG, "Error during selection PPSE request: %d", err); | ||||||
|                     furi_hal_nfc_deactivate(); |                     furi_hal_nfc_deactivate(); | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|                 FURI_LOG_I( |                 FURI_LOG_I(TAG, "Select PPSE response received. Start parsing response"); | ||||||
|                     NFC_WORKER_TAG, "Select PPSE response received. Start parsing response"); |  | ||||||
|                 if(emv_decode_ppse_response(rx_buff, *rx_len, &emv_app)) { |                 if(emv_decode_ppse_response(rx_buff, *rx_len, &emv_app)) { | ||||||
|                     FURI_LOG_I(NFC_WORKER_TAG, "Select PPSE responce parced"); |                     FURI_LOG_I(TAG, "Select PPSE responce parced"); | ||||||
|                     // Notify caller and exit
 |                     // Notify caller and exit
 | ||||||
|                     result->emv_data.aid_len = emv_app.aid_len; |                     result->emv_data.aid_len = emv_app.aid_len; | ||||||
|                     memcpy(result->emv_data.aid, emv_app.aid, emv_app.aid_len); |                     memcpy(result->emv_data.aid, emv_app.aid, emv_app.aid_len); | ||||||
| @ -194,18 +193,18 @@ void nfc_worker_read_emv_app(NfcWorker* nfc_worker) { | |||||||
|                     } |                     } | ||||||
|                     break; |                     break; | ||||||
|                 } else { |                 } else { | ||||||
|                     FURI_LOG_E(NFC_WORKER_TAG, "Can't find pay application"); |                     FURI_LOG_E(TAG, "Can't find pay application"); | ||||||
|                     furi_hal_nfc_deactivate(); |                     furi_hal_nfc_deactivate(); | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 // Can't find EMV card
 |                 // Can't find EMV card
 | ||||||
|                 FURI_LOG_W(NFC_WORKER_TAG, "Card doesn't support EMV"); |                 FURI_LOG_W(TAG, "Card doesn't support EMV"); | ||||||
|                 furi_hal_nfc_deactivate(); |                 furi_hal_nfc_deactivate(); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             // Can't find EMV card
 |             // Can't find EMV card
 | ||||||
|             FURI_LOG_W(NFC_WORKER_TAG, "Can't find any cards"); |             FURI_LOG_W(TAG, "Can't find any cards"); | ||||||
|             furi_hal_nfc_deactivate(); |             furi_hal_nfc_deactivate(); | ||||||
|         } |         } | ||||||
|         osDelay(20); |         osDelay(20); | ||||||
| @ -236,56 +235,53 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { | |||||||
|                     result->nfc_data.uid, dev_list[0].dev.nfca.nfcId1, result->nfc_data.uid_len); |                     result->nfc_data.uid, dev_list[0].dev.nfca.nfcId1, result->nfc_data.uid_len); | ||||||
|                 result->nfc_data.protocol = NfcDeviceProtocolEMV; |                 result->nfc_data.protocol = NfcDeviceProtocolEMV; | ||||||
| 
 | 
 | ||||||
|                 FURI_LOG_I(NFC_WORKER_TAG, "Send select PPSE command"); |                 FURI_LOG_I(TAG, "Send select PPSE command"); | ||||||
|                 tx_len = emv_prepare_select_ppse(tx_buff); |                 tx_len = emv_prepare_select_ppse(tx_buff); | ||||||
|                 err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); |                 err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); | ||||||
|                 if(err != ERR_NONE) { |                 if(err != ERR_NONE) { | ||||||
|                     FURI_LOG_E(NFC_WORKER_TAG, "Error during selection PPSE request: %d", err); |                     FURI_LOG_E(TAG, "Error during selection PPSE request: %d", err); | ||||||
|                     furi_hal_nfc_deactivate(); |                     furi_hal_nfc_deactivate(); | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|                 FURI_LOG_I( |                 FURI_LOG_I(TAG, "Select PPSE response received. Start parsing response"); | ||||||
|                     NFC_WORKER_TAG, "Select PPSE response received. Start parsing response"); |  | ||||||
|                 if(emv_decode_ppse_response(rx_buff, *rx_len, &emv_app)) { |                 if(emv_decode_ppse_response(rx_buff, *rx_len, &emv_app)) { | ||||||
|                     FURI_LOG_I(NFC_WORKER_TAG, "Select PPSE responce parced"); |                     FURI_LOG_I(TAG, "Select PPSE responce parced"); | ||||||
|  |                     result->emv_data.aid_len = emv_app.aid_len; | ||||||
|  |                     memcpy(result->emv_data.aid, emv_app.aid, emv_app.aid_len); | ||||||
|                 } else { |                 } else { | ||||||
|                     FURI_LOG_E(NFC_WORKER_TAG, "Can't find pay application"); |                     FURI_LOG_E(TAG, "Can't find pay application"); | ||||||
|                     furi_hal_nfc_deactivate(); |                     furi_hal_nfc_deactivate(); | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|                 FURI_LOG_I(NFC_WORKER_TAG, "Starting application ..."); |                 FURI_LOG_I(TAG, "Starting application ..."); | ||||||
|                 tx_len = emv_prepare_select_app(tx_buff, &emv_app); |                 tx_len = emv_prepare_select_app(tx_buff, &emv_app); | ||||||
|                 err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); |                 err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); | ||||||
|                 if(err != ERR_NONE) { |                 if(err != ERR_NONE) { | ||||||
|                     FURI_LOG_E( |                     FURI_LOG_E(TAG, "Error during application selection request: %d", err); | ||||||
|                         NFC_WORKER_TAG, "Error during application selection request: %d", err); |  | ||||||
|                     furi_hal_nfc_deactivate(); |                     furi_hal_nfc_deactivate(); | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|                 FURI_LOG_I( |                 FURI_LOG_I(TAG, "Select application response received. Start parsing response"); | ||||||
|                     NFC_WORKER_TAG, |  | ||||||
|                     "Select application response received. Start parsing response"); |  | ||||||
|                 if(emv_decode_select_app_response(rx_buff, *rx_len, &emv_app)) { |                 if(emv_decode_select_app_response(rx_buff, *rx_len, &emv_app)) { | ||||||
|                     FURI_LOG_I(NFC_WORKER_TAG, "Card name: %s", emv_app.name); |                     FURI_LOG_I(TAG, "Card name: %s", emv_app.name); | ||||||
|                     memcpy(result->emv_data.name, emv_app.name, sizeof(emv_app.name)); |                     memcpy(result->emv_data.name, emv_app.name, sizeof(emv_app.name)); | ||||||
|                 } else if(emv_app.pdol.size > 0) { |                 } else if(emv_app.pdol.size > 0) { | ||||||
|                     FURI_LOG_W(NFC_WORKER_TAG, "Can't find card name, but PDOL is present."); |                     FURI_LOG_W(TAG, "Can't find card name, but PDOL is present."); | ||||||
|                 } else { |                 } else { | ||||||
|                     FURI_LOG_E(NFC_WORKER_TAG, "Can't find card name or PDOL"); |                     FURI_LOG_E(TAG, "Can't find card name or PDOL"); | ||||||
|                     furi_hal_nfc_deactivate(); |                     furi_hal_nfc_deactivate(); | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|                 FURI_LOG_I(NFC_WORKER_TAG, "Starting Get Processing Options command ..."); |                 FURI_LOG_I(TAG, "Starting Get Processing Options command ..."); | ||||||
|                 tx_len = emv_prepare_get_proc_opt(tx_buff, &emv_app); |                 tx_len = emv_prepare_get_proc_opt(tx_buff, &emv_app); | ||||||
|                 err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); |                 err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); | ||||||
|                 if(err != ERR_NONE) { |                 if(err != ERR_NONE) { | ||||||
|                     FURI_LOG_E( |                     FURI_LOG_E(TAG, "Error during Get Processing Options command: %d", err); | ||||||
|                         NFC_WORKER_TAG, "Error during Get Processing Options command: %d", err); |  | ||||||
|                     furi_hal_nfc_deactivate(); |                     furi_hal_nfc_deactivate(); | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|                 if(emv_decode_get_proc_opt(rx_buff, *rx_len, &emv_app)) { |                 if(emv_decode_get_proc_opt(rx_buff, *rx_len, &emv_app)) { | ||||||
|                     FURI_LOG_I(NFC_WORKER_TAG, "Card number parsed"); |                     FURI_LOG_I(TAG, "Card number parsed"); | ||||||
|                     result->emv_data.number_len = emv_app.card_number_len; |                     result->emv_data.number_len = emv_app.card_number_len; | ||||||
|                     memcpy(result->emv_data.number, emv_app.card_number, emv_app.card_number_len); |                     memcpy(result->emv_data.number, emv_app.card_number, emv_app.card_number_len); | ||||||
|                     // Notify caller and exit
 |                     // Notify caller and exit
 | ||||||
| @ -309,7 +305,7 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { | |||||||
|                                 tx_buff, tx_len, &rx_buff, &rx_len, false); |                                 tx_buff, tx_len, &rx_buff, &rx_len, false); | ||||||
|                             if(err != ERR_NONE) { |                             if(err != ERR_NONE) { | ||||||
|                                 FURI_LOG_E( |                                 FURI_LOG_E( | ||||||
|                                     NFC_WORKER_TAG, |                                     TAG, | ||||||
|                                     "Error reading application sfi %d, record %d", |                                     "Error reading application sfi %d, record %d", | ||||||
|                                     sfi, |                                     sfi, | ||||||
|                                     record); |                                     record); | ||||||
| @ -321,7 +317,7 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { | |||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     if(pan_found) { |                     if(pan_found) { | ||||||
|                         FURI_LOG_I(NFC_WORKER_TAG, "Card PAN found"); |                         FURI_LOG_I(TAG, "Card PAN found"); | ||||||
|                         result->emv_data.number_len = emv_app.card_number_len; |                         result->emv_data.number_len = emv_app.card_number_len; | ||||||
|                         memcpy( |                         memcpy( | ||||||
|                             result->emv_data.number, |                             result->emv_data.number, | ||||||
| @ -343,18 +339,18 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { | |||||||
|                         } |                         } | ||||||
|                         break; |                         break; | ||||||
|                     } else { |                     } else { | ||||||
|                         FURI_LOG_E(NFC_WORKER_TAG, "Can't read card number"); |                         FURI_LOG_E(TAG, "Can't read card number"); | ||||||
|                     } |                     } | ||||||
|                     furi_hal_nfc_deactivate(); |                     furi_hal_nfc_deactivate(); | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 // Can't find EMV card
 |                 // Can't find EMV card
 | ||||||
|                 FURI_LOG_W(NFC_WORKER_TAG, "Card doesn't support EMV"); |                 FURI_LOG_W(TAG, "Card doesn't support EMV"); | ||||||
|                 furi_hal_nfc_deactivate(); |                 furi_hal_nfc_deactivate(); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             // Can't find EMV card
 |             // Can't find EMV card
 | ||||||
|             FURI_LOG_W(NFC_WORKER_TAG, "Can't find any cards"); |             FURI_LOG_W(TAG, "Can't find any cards"); | ||||||
|             furi_hal_nfc_deactivate(); |             furi_hal_nfc_deactivate(); | ||||||
|         } |         } | ||||||
|         osDelay(20); |         osDelay(20); | ||||||
| @ -416,63 +412,63 @@ void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { | |||||||
| 
 | 
 | ||||||
|     while(nfc_worker->state == NfcWorkerStateEmulateApdu) { |     while(nfc_worker->state == NfcWorkerStateEmulateApdu) { | ||||||
|         if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 300)) { |         if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 300)) { | ||||||
|             FURI_LOG_I(NFC_WORKER_TAG, "POS terminal detected"); |             FURI_LOG_I(TAG, "POS terminal detected"); | ||||||
|             // Read data from POS terminal
 |             // Read data from POS terminal
 | ||||||
|             err = furi_hal_nfc_data_exchange(NULL, 0, &rx_buff, &rx_len, false); |             err = furi_hal_nfc_data_exchange(NULL, 0, &rx_buff, &rx_len, false); | ||||||
|             if(err == ERR_NONE) { |             if(err == ERR_NONE) { | ||||||
|                 FURI_LOG_I(NFC_WORKER_TAG, "Received Select PPSE"); |                 FURI_LOG_I(TAG, "Received Select PPSE"); | ||||||
|             } else { |             } else { | ||||||
|                 FURI_LOG_E(NFC_WORKER_TAG, "Error in 1st data exchange: select PPSE"); |                 FURI_LOG_E(TAG, "Error in 1st data exchange: select PPSE"); | ||||||
|                 furi_hal_nfc_deactivate(); |                 furi_hal_nfc_deactivate(); | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|             FURI_LOG_I(NFC_WORKER_TAG, "Transive SELECT PPSE ANS"); |             FURI_LOG_I(TAG, "Transive SELECT PPSE ANS"); | ||||||
|             tx_len = emv_select_ppse_ans(tx_buff); |             tx_len = emv_select_ppse_ans(tx_buff); | ||||||
|             err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); |             err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); | ||||||
|             if(err == ERR_NONE) { |             if(err == ERR_NONE) { | ||||||
|                 FURI_LOG_I(NFC_WORKER_TAG, "Received Select APP"); |                 FURI_LOG_I(TAG, "Received Select APP"); | ||||||
|             } else { |             } else { | ||||||
|                 FURI_LOG_E(NFC_WORKER_TAG, "Error in 2nd data exchange: select APP"); |                 FURI_LOG_E(TAG, "Error in 2nd data exchange: select APP"); | ||||||
|                 furi_hal_nfc_deactivate(); |                 furi_hal_nfc_deactivate(); | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             FURI_LOG_I(NFC_WORKER_TAG, "Transive SELECT APP ANS"); |             FURI_LOG_I(TAG, "Transive SELECT APP ANS"); | ||||||
|             tx_len = emv_select_app_ans(tx_buff); |             tx_len = emv_select_app_ans(tx_buff); | ||||||
|             err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); |             err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); | ||||||
|             if(err == ERR_NONE) { |             if(err == ERR_NONE) { | ||||||
|                 FURI_LOG_I(NFC_WORKER_TAG, "Received PDOL"); |                 FURI_LOG_I(TAG, "Received PDOL"); | ||||||
|             } else { |             } else { | ||||||
|                 FURI_LOG_E(NFC_WORKER_TAG, "Error in 3rd data exchange: receive PDOL"); |                 FURI_LOG_E(TAG, "Error in 3rd data exchange: receive PDOL"); | ||||||
|                 furi_hal_nfc_deactivate(); |                 furi_hal_nfc_deactivate(); | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             FURI_LOG_I(NFC_WORKER_TAG, "Transive PDOL ANS"); |             FURI_LOG_I(TAG, "Transive PDOL ANS"); | ||||||
|             tx_len = emv_get_proc_opt_ans(tx_buff); |             tx_len = emv_get_proc_opt_ans(tx_buff); | ||||||
|             err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); |             err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); | ||||||
|             if(err == ERR_NONE) { |             if(err == ERR_NONE) { | ||||||
|                 FURI_LOG_I(NFC_WORKER_TAG, "Transive PDOL ANS"); |                 FURI_LOG_I(TAG, "Transive PDOL ANS"); | ||||||
|             } else { |             } else { | ||||||
|                 FURI_LOG_E(NFC_WORKER_TAG, "Error in 4rd data exchange: Transive PDOL ANS"); |                 FURI_LOG_E(TAG, "Error in 4rd data exchange: Transive PDOL ANS"); | ||||||
|                 furi_hal_nfc_deactivate(); |                 furi_hal_nfc_deactivate(); | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if(*rx_len != sizeof(debug_rx) || memcmp(rx_buff, debug_rx, sizeof(debug_rx))) { |             if(*rx_len != sizeof(debug_rx) || memcmp(rx_buff, debug_rx, sizeof(debug_rx))) { | ||||||
|                 FURI_LOG_E(NFC_WORKER_TAG, "Failed long message test"); |                 FURI_LOG_E(TAG, "Failed long message test"); | ||||||
|             } else { |             } else { | ||||||
|                 FURI_LOG_I(NFC_WORKER_TAG, "Correct debug message received"); |                 FURI_LOG_I(TAG, "Correct debug message received"); | ||||||
|                 tx_len = sizeof(debug_tx); |                 tx_len = sizeof(debug_tx); | ||||||
|                 err = furi_hal_nfc_data_exchange( |                 err = furi_hal_nfc_data_exchange( | ||||||
|                     (uint8_t*)debug_tx, tx_len, &rx_buff, &rx_len, false); |                     (uint8_t*)debug_tx, tx_len, &rx_buff, &rx_len, false); | ||||||
|                 if(err == ERR_NONE) { |                 if(err == ERR_NONE) { | ||||||
|                     FURI_LOG_I(NFC_WORKER_TAG, "Transive Debug message"); |                     FURI_LOG_I(TAG, "Transive Debug message"); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             furi_hal_nfc_deactivate(); |             furi_hal_nfc_deactivate(); | ||||||
|         } else { |         } else { | ||||||
|             FURI_LOG_W(NFC_WORKER_TAG, "Can't find reader"); |             FURI_LOG_W(TAG, "Can't find reader"); | ||||||
|         } |         } | ||||||
|         osDelay(20); |         osDelay(20); | ||||||
|     } |     } | ||||||
| @ -499,71 +495,69 @@ void nfc_worker_read_mifare_ul(NfcWorker* nfc_worker) { | |||||||
|                    dev_list[0].dev.nfca.sensRes.platformInfo, |                    dev_list[0].dev.nfca.sensRes.platformInfo, | ||||||
|                    dev_list[0].dev.nfca.selRes.sak)) { |                    dev_list[0].dev.nfca.selRes.sak)) { | ||||||
|                 // Get Mifare Ultralight version
 |                 // Get Mifare Ultralight version
 | ||||||
|                 FURI_LOG_I(NFC_WORKER_TAG, "Found Mifare Ultralight tag. Reading tag version"); |                 FURI_LOG_I(TAG, "Found Mifare Ultralight tag. Reading tag version"); | ||||||
|                 tx_len = mf_ul_prepare_get_version(tx_buff); |                 tx_len = mf_ul_prepare_get_version(tx_buff); | ||||||
|                 err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); |                 err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); | ||||||
|                 if(err == ERR_NONE) { |                 if(err == ERR_NONE) { | ||||||
|                     mf_ul_parse_get_version_response(rx_buff, &mf_ul_read); |                     mf_ul_parse_get_version_response(rx_buff, &mf_ul_read); | ||||||
|                     FURI_LOG_I( |                     FURI_LOG_I( | ||||||
|                         NFC_WORKER_TAG, |                         TAG, | ||||||
|                         "Mifare Ultralight Type: %d, Pages: %d", |                         "Mifare Ultralight Type: %d, Pages: %d", | ||||||
|                         mf_ul_read.type, |                         mf_ul_read.type, | ||||||
|                         mf_ul_read.pages_to_read); |                         mf_ul_read.pages_to_read); | ||||||
|                     FURI_LOG_I(NFC_WORKER_TAG, "Reading signature ..."); |                     FURI_LOG_I(TAG, "Reading signature ..."); | ||||||
|                     tx_len = mf_ul_prepare_read_signature(tx_buff); |                     tx_len = mf_ul_prepare_read_signature(tx_buff); | ||||||
|                     if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { |                     if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { | ||||||
|                         FURI_LOG_W(NFC_WORKER_TAG, "Failed reading signature"); |                         FURI_LOG_W(TAG, "Failed reading signature"); | ||||||
|                         memset(mf_ul_read.data.signature, 0, sizeof(mf_ul_read.data.signature)); |                         memset(mf_ul_read.data.signature, 0, sizeof(mf_ul_read.data.signature)); | ||||||
|                     } else { |                     } else { | ||||||
|                         mf_ul_parse_read_signature_response(rx_buff, &mf_ul_read); |                         mf_ul_parse_read_signature_response(rx_buff, &mf_ul_read); | ||||||
|                     } |                     } | ||||||
|                 } else if(err == ERR_TIMEOUT) { |                 } else if(err == ERR_TIMEOUT) { | ||||||
|                     FURI_LOG_W( |                     FURI_LOG_W( | ||||||
|                         NFC_WORKER_TAG, |                         TAG, | ||||||
|                         "Card doesn't respond to GET VERSION command. Setting default read parameters"); |                         "Card doesn't respond to GET VERSION command. Setting default read parameters"); | ||||||
|                     err = ERR_NONE; |                     err = ERR_NONE; | ||||||
|                     mf_ul_set_default_version(&mf_ul_read); |                     mf_ul_set_default_version(&mf_ul_read); | ||||||
|                     // Reinit device
 |                     // Reinit device
 | ||||||
|                     furi_hal_nfc_deactivate(); |                     furi_hal_nfc_deactivate(); | ||||||
|                     if(!furi_hal_nfc_detect(&dev_list, &dev_cnt, 300, false)) { |                     if(!furi_hal_nfc_detect(&dev_list, &dev_cnt, 300, false)) { | ||||||
|                         FURI_LOG_E(NFC_WORKER_TAG, "Lost connection. Restarting search"); |                         FURI_LOG_E(TAG, "Lost connection. Restarting search"); | ||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     FURI_LOG_E( |                     FURI_LOG_E( | ||||||
|                         NFC_WORKER_TAG, |                         TAG, "Error getting Mifare Ultralight version. Error code: %d", err); | ||||||
|                         "Error getting Mifare Ultralight version. Error code: %d", |  | ||||||
|                         err); |  | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 if(mf_ul_read.support_fast_read) { |                 if(mf_ul_read.support_fast_read) { | ||||||
|                     FURI_LOG_I(NFC_WORKER_TAG, "Reading pages ..."); |                     FURI_LOG_I(TAG, "Reading pages ..."); | ||||||
|                     tx_len = mf_ul_prepare_fast_read(tx_buff, 0x00, mf_ul_read.pages_to_read - 1); |                     tx_len = mf_ul_prepare_fast_read(tx_buff, 0x00, mf_ul_read.pages_to_read - 1); | ||||||
|                     if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { |                     if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { | ||||||
|                         FURI_LOG_E(NFC_WORKER_TAG, "Failed reading pages"); |                         FURI_LOG_E(TAG, "Failed reading pages"); | ||||||
|                         continue; |                         continue; | ||||||
|                     } else { |                     } else { | ||||||
|                         mf_ul_parse_fast_read_response( |                         mf_ul_parse_fast_read_response( | ||||||
|                             rx_buff, 0x00, mf_ul_read.pages_to_read - 1, &mf_ul_read); |                             rx_buff, 0x00, mf_ul_read.pages_to_read - 1, &mf_ul_read); | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     FURI_LOG_I(NFC_WORKER_TAG, "Reading 3 counters ..."); |                     FURI_LOG_I(TAG, "Reading 3 counters ..."); | ||||||
|                     for(uint8_t i = 0; i < 3; i++) { |                     for(uint8_t i = 0; i < 3; i++) { | ||||||
|                         tx_len = mf_ul_prepare_read_cnt(tx_buff, i); |                         tx_len = mf_ul_prepare_read_cnt(tx_buff, i); | ||||||
|                         if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { |                         if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { | ||||||
|                             FURI_LOG_W(NFC_WORKER_TAG, "Failed reading Counter %d", i); |                             FURI_LOG_W(TAG, "Failed reading Counter %d", i); | ||||||
|                             mf_ul_read.data.counter[i] = 0; |                             mf_ul_read.data.counter[i] = 0; | ||||||
|                         } else { |                         } else { | ||||||
|                             mf_ul_parse_read_cnt_response(rx_buff, i, &mf_ul_read); |                             mf_ul_parse_read_cnt_response(rx_buff, i, &mf_ul_read); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     FURI_LOG_I(NFC_WORKER_TAG, "Checking tearing flags ..."); |                     FURI_LOG_I(TAG, "Checking tearing flags ..."); | ||||||
|                     for(uint8_t i = 0; i < 3; i++) { |                     for(uint8_t i = 0; i < 3; i++) { | ||||||
|                         tx_len = mf_ul_prepare_check_tearing(tx_buff, i); |                         tx_len = mf_ul_prepare_check_tearing(tx_buff, i); | ||||||
|                         if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { |                         if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { | ||||||
|                             FURI_LOG_E(NFC_WORKER_TAG, "Error checking tearing flag %d", i); |                             FURI_LOG_E(TAG, "Error checking tearing flag %d", i); | ||||||
|                             mf_ul_read.data.tearing[i] = MF_UL_TEARING_FLAG_DEFAULT; |                             mf_ul_read.data.tearing[i] = MF_UL_TEARING_FLAG_DEFAULT; | ||||||
|                         } else { |                         } else { | ||||||
|                             mf_ul_parse_check_tearing_response(rx_buff, i, &mf_ul_read); |                             mf_ul_parse_check_tearing_response(rx_buff, i, &mf_ul_read); | ||||||
| @ -572,11 +566,10 @@ void nfc_worker_read_mifare_ul(NfcWorker* nfc_worker) { | |||||||
|                 } else { |                 } else { | ||||||
|                     // READ card with READ command (4 pages at a time)
 |                     // READ card with READ command (4 pages at a time)
 | ||||||
|                     for(uint8_t page = 0; page < mf_ul_read.pages_to_read; page += 4) { |                     for(uint8_t page = 0; page < mf_ul_read.pages_to_read; page += 4) { | ||||||
|                         FURI_LOG_I(NFC_WORKER_TAG, "Reading pages %d - %d ...", page, page + 3); |                         FURI_LOG_I(TAG, "Reading pages %d - %d ...", page, page + 3); | ||||||
|                         tx_len = mf_ul_prepare_read(tx_buff, page); |                         tx_len = mf_ul_prepare_read(tx_buff, page); | ||||||
|                         if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { |                         if(furi_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false)) { | ||||||
|                             FURI_LOG_E( |                             FURI_LOG_E(TAG, "Read pages %d - %d failed", page, page + 3); | ||||||
|                                 NFC_WORKER_TAG, "Read pages %d - %d failed", page, page + 3); |  | ||||||
|                             continue; |                             continue; | ||||||
|                         } else { |                         } else { | ||||||
|                             mf_ul_parse_read_response(rx_buff, page, &mf_ul_read); |                             mf_ul_parse_read_response(rx_buff, page, &mf_ul_read); | ||||||
| @ -600,10 +593,10 @@ void nfc_worker_read_mifare_ul(NfcWorker* nfc_worker) { | |||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|             } else { |             } else { | ||||||
|                 FURI_LOG_W(NFC_WORKER_TAG, "Tag does not support Mifare Ultralight"); |                 FURI_LOG_W(TAG, "Tag does not support Mifare Ultralight"); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             FURI_LOG_W(NFC_WORKER_TAG, "Can't find any tags"); |             FURI_LOG_W(TAG, "Can't find any tags"); | ||||||
|         } |         } | ||||||
|         osDelay(100); |         osDelay(100); | ||||||
|     } |     } | ||||||
| @ -627,7 +620,7 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) { | |||||||
|                data->nfc_data.sak, |                data->nfc_data.sak, | ||||||
|                true, |                true, | ||||||
|                200)) { |                200)) { | ||||||
|             FURI_LOG_D(NFC_WORKER_TAG, "Anticollision passed"); |             FURI_LOG_D(TAG, "Anticollision passed"); | ||||||
|             if(furi_hal_nfc_get_first_frame(&rx_buff, &rx_len)) { |             if(furi_hal_nfc_get_first_frame(&rx_buff, &rx_len)) { | ||||||
|                 // Data exchange loop
 |                 // Data exchange loop
 | ||||||
|                 while(nfc_worker->state == NfcWorkerStateEmulateMifareUl) { |                 while(nfc_worker->state == NfcWorkerStateEmulateMifareUl) { | ||||||
| @ -639,17 +632,17 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) { | |||||||
|                         if(err == ERR_NONE) { |                         if(err == ERR_NONE) { | ||||||
|                             continue; |                             continue; | ||||||
|                         } else { |                         } else { | ||||||
|                             FURI_LOG_E(NFC_WORKER_TAG, "Communication error: %d", err); |                             FURI_LOG_E(TAG, "Communication error: %d", err); | ||||||
|                             break; |                             break; | ||||||
|                         } |                         } | ||||||
|                     } else { |                     } else { | ||||||
|                         FURI_LOG_W(NFC_WORKER_TAG, "Not valid command: %02X", rx_buff[0]); |                         FURI_LOG_W(TAG, "Not valid command: %02X", rx_buff[0]); | ||||||
|                         furi_hal_nfc_deactivate(); |                         furi_hal_nfc_deactivate(); | ||||||
|                         break; |                         break; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 FURI_LOG_W(NFC_WORKER_TAG, "Error in 1st data exchange"); |                 FURI_LOG_W(TAG, "Error in 1st data exchange"); | ||||||
|                 furi_hal_nfc_deactivate(); |                 furi_hal_nfc_deactivate(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -660,7 +653,7 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) { | |||||||
|                 nfc_worker->callback(nfc_worker->context); |                 nfc_worker->callback(nfc_worker->context); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         FURI_LOG_W(NFC_WORKER_TAG, "Can't find reader"); |         FURI_LOG_W(TAG, "Can't find reader"); | ||||||
|         osThreadYield(); |         osThreadYield(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ void nfc_scene_card_menu_on_enter(void* context) { | |||||||
|     Nfc* nfc = (Nfc*)context; |     Nfc* nfc = (Nfc*)context; | ||||||
|     Submenu* submenu = nfc->submenu; |     Submenu* submenu = nfc->submenu; | ||||||
| 
 | 
 | ||||||
|     if(nfc->dev.dev_data.nfc_data.protocol > NfcDeviceProtocolUnknown) { |     if(nfc->dev->dev_data.nfc_data.protocol > NfcDeviceProtocolUnknown) { | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
|             submenu, |             submenu, | ||||||
|             "Run compatible app", |             "Run compatible app", | ||||||
| @ -48,9 +48,9 @@ bool nfc_scene_card_menu_on_event(void* context, SceneManagerEvent event) { | |||||||
|         if(event.event == SubmenuIndexRunApp) { |         if(event.event == SubmenuIndexRunApp) { | ||||||
|             scene_manager_set_scene_state( |             scene_manager_set_scene_state( | ||||||
|                 nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexRunApp); |                 nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexRunApp); | ||||||
|             if(nfc->dev.dev_data.nfc_data.protocol == NfcDeviceProtocolMifareUl) { |             if(nfc->dev->dev_data.nfc_data.protocol == NfcDeviceProtocolMifareUl) { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareUl); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareUl); | ||||||
|             } else if(nfc->dev.dev_data.nfc_data.protocol == NfcDeviceProtocolEMV) { |             } else if(nfc->dev->dev_data.nfc_data.protocol == NfcDeviceProtocolEMV) { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneReadEmvApp); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneReadEmvApp); | ||||||
|             } |             } | ||||||
|             return true; |             return true; | ||||||
| @ -66,7 +66,7 @@ bool nfc_scene_card_menu_on_event(void* context, SceneManagerEvent event) { | |||||||
|             return true; |             return true; | ||||||
|         } else if(event.event == SubmenuIndexSave) { |         } else if(event.event == SubmenuIndexSave) { | ||||||
|             scene_manager_set_scene_state(nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexSave); |             scene_manager_set_scene_state(nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexSave); | ||||||
|             nfc->dev.format = NfcDeviceSaveFormatUid; |             nfc->dev->format = NfcDeviceSaveFormatUid; | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -12,14 +12,14 @@ void nfc_scene_delete_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     // Setup Custom Widget view
 |     // Setup Custom Widget view
 | ||||||
|     char delete_str[64]; |     char delete_str[64]; | ||||||
|     snprintf(delete_str, sizeof(delete_str), "Delete %s", nfc->dev.dev_name); |     snprintf(delete_str, sizeof(delete_str), "\e#Delete %s\e#", nfc->dev->dev_name); | ||||||
|     widget_add_string_element(nfc->widget, 64, 6, AlignCenter, AlignTop, FontPrimary, delete_str); |     widget_add_text_box_element(nfc->widget, 0, 0, 128, 24, AlignCenter, AlignCenter, delete_str); | ||||||
|     widget_add_button_element( |     widget_add_button_element( | ||||||
|         nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_delete_widget_callback, nfc); |         nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_delete_widget_callback, nfc); | ||||||
|     widget_add_button_element( |     widget_add_button_element( | ||||||
|         nfc->widget, GuiButtonTypeRight, "Delete", nfc_scene_delete_widget_callback, nfc); |         nfc->widget, GuiButtonTypeRight, "Delete", nfc_scene_delete_widget_callback, nfc); | ||||||
|     char uid_str[32]; |     char uid_str[32]; | ||||||
|     NfcDeviceCommonData* data = &nfc->dev.dev_data.nfc_data; |     NfcDeviceCommonData* data = &nfc->dev->dev_data.nfc_data; | ||||||
|     if(data->uid_len == 4) { |     if(data->uid_len == 4) { | ||||||
|         snprintf( |         snprintf( | ||||||
|             uid_str, |             uid_str, | ||||||
| @ -73,7 +73,7 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { | |||||||
|         if(event.event == GuiButtonTypeLeft) { |         if(event.event == GuiButtonTypeLeft) { | ||||||
|             return scene_manager_previous_scene(nfc->scene_manager); |             return scene_manager_previous_scene(nfc->scene_manager); | ||||||
|         } else if(event.event == GuiButtonTypeRight) { |         } else if(event.event == GuiButtonTypeRight) { | ||||||
|             if(nfc_device_delete(&nfc->dev)) { |             if(nfc_device_delete(nfc->dev)) { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); | ||||||
|             } else { |             } else { | ||||||
|                 scene_manager_search_and_switch_to_previous_scene( |                 scene_manager_search_and_switch_to_previous_scene( | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "../nfc_i.h" | #include "../nfc_i.h" | ||||||
|  | #include "../helpers/nfc_emv_parser.h" | ||||||
| 
 | 
 | ||||||
| #define NFC_SCENE_DEVICE_INFO_BACK_EVENT (0UL) | #define NFC_SCENE_DEVICE_INFO_BACK_EVENT (0UL) | ||||||
| 
 | 
 | ||||||
| @ -35,14 +36,14 @@ void nfc_scene_device_info_on_enter(void* context) { | |||||||
|     Nfc* nfc = context; |     Nfc* nfc = context; | ||||||
| 
 | 
 | ||||||
|     // Setup Custom Widget view
 |     // Setup Custom Widget view
 | ||||||
|     widget_add_string_element( |     widget_add_text_box_element( | ||||||
|         nfc->widget, 64, 6, AlignCenter, AlignTop, FontSecondary, nfc->dev.dev_name); |         nfc->widget, 0, 0, 128, 24, AlignCenter, AlignCenter, nfc->dev->dev_name); | ||||||
|     widget_add_button_element( |     widget_add_button_element( | ||||||
|         nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_device_info_widget_callback, nfc); |         nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_device_info_widget_callback, nfc); | ||||||
|     widget_add_button_element( |     widget_add_button_element( | ||||||
|         nfc->widget, GuiButtonTypeRight, "Data", nfc_scene_device_info_widget_callback, nfc); |         nfc->widget, GuiButtonTypeRight, "Data", nfc_scene_device_info_widget_callback, nfc); | ||||||
|     char uid_str[32]; |     char uid_str[32]; | ||||||
|     NfcDeviceCommonData* data = &nfc->dev.dev_data.nfc_data; |     NfcDeviceCommonData* data = &nfc->dev->dev_data.nfc_data; | ||||||
|     if(data->uid_len == 4) { |     if(data->uid_len == 4) { | ||||||
|         snprintf( |         snprintf( | ||||||
|             uid_str, |             uid_str, | ||||||
| @ -87,14 +88,14 @@ void nfc_scene_device_info_on_enter(void* context) { | |||||||
|     widget_add_string_element(nfc->widget, 118, 42, AlignRight, AlignTop, FontSecondary, atqa_str); |     widget_add_string_element(nfc->widget, 118, 42, AlignRight, AlignTop, FontSecondary, atqa_str); | ||||||
| 
 | 
 | ||||||
|     // Setup Data View
 |     // Setup Data View
 | ||||||
|     if(nfc->dev.format == NfcDeviceSaveFormatUid) { |     if(nfc->dev->format == NfcDeviceSaveFormatUid) { | ||||||
|         DialogEx* dialog_ex = nfc->dialog_ex; |         DialogEx* dialog_ex = nfc->dialog_ex; | ||||||
|         dialog_ex_set_left_button_text(dialog_ex, "Back"); |         dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||||
|         dialog_ex_set_text(dialog_ex, "No data", 64, 32, AlignCenter, AlignCenter); |         dialog_ex_set_text(dialog_ex, "No data", 64, 32, AlignCenter, AlignCenter); | ||||||
|         dialog_ex_set_context(dialog_ex, nfc); |         dialog_ex_set_context(dialog_ex, nfc); | ||||||
|         dialog_ex_set_result_callback(dialog_ex, nfc_scene_device_info_dialog_callback); |         dialog_ex_set_result_callback(dialog_ex, nfc_scene_device_info_dialog_callback); | ||||||
|     } else if(nfc->dev.format == NfcDeviceSaveFormatMifareUl) { |     } else if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { | ||||||
|         MifareUlData* mf_ul_data = (MifareUlData*)&nfc->dev.dev_data.mf_ul_data; |         MifareUlData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; | ||||||
|         TextBox* text_box = nfc->text_box; |         TextBox* text_box = nfc->text_box; | ||||||
|         text_box_set_context(text_box, nfc); |         text_box_set_context(text_box, nfc); | ||||||
|         text_box_set_exit_callback(text_box, nfc_scene_device_info_text_box_callback); |         text_box_set_exit_callback(text_box, nfc_scene_device_info_text_box_callback); | ||||||
| @ -107,8 +108,8 @@ void nfc_scene_device_info_on_enter(void* context) { | |||||||
|                 nfc->text_box_store, "%02X%02X ", mf_ul_data->data[i], mf_ul_data->data[i + 1]); |                 nfc->text_box_store, "%02X%02X ", mf_ul_data->data[i], mf_ul_data->data[i + 1]); | ||||||
|         } |         } | ||||||
|         text_box_set_text(text_box, string_get_cstr(nfc->text_box_store)); |         text_box_set_text(text_box, string_get_cstr(nfc->text_box_store)); | ||||||
|     } else if(nfc->dev.format == NfcDeviceSaveFormatBankCard) { |     } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { | ||||||
|         NfcEmvData* emv_data = &nfc->dev.dev_data.emv_data; |         NfcEmvData* emv_data = &nfc->dev->dev_data.emv_data; | ||||||
|         BankCard* bank_card = nfc->bank_card; |         BankCard* bank_card = nfc->bank_card; | ||||||
|         bank_card_set_name(bank_card, emv_data->name); |         bank_card_set_name(bank_card, emv_data->name); | ||||||
|         bank_card_set_number(bank_card, emv_data->number, emv_data->number_len); |         bank_card_set_number(bank_card, emv_data->number, emv_data->number_len); | ||||||
| @ -116,12 +117,29 @@ void nfc_scene_device_info_on_enter(void* context) { | |||||||
|         if(emv_data->exp_mon) { |         if(emv_data->exp_mon) { | ||||||
|             bank_card_set_exp_date(bank_card, emv_data->exp_mon, emv_data->exp_year); |             bank_card_set_exp_date(bank_card, emv_data->exp_mon, emv_data->exp_year); | ||||||
|         } |         } | ||||||
|  |         string_t display_str; | ||||||
|  |         string_init(display_str); | ||||||
|         if(emv_data->country_code) { |         if(emv_data->country_code) { | ||||||
|             bank_card_set_country_name(bank_card, emv_data->country_code); |             string_t country_name; | ||||||
|  |             string_init(country_name); | ||||||
|  |             if(nfc_emv_parser_get_country_name( | ||||||
|  |                    nfc->dev->storage, emv_data->country_code, country_name)) { | ||||||
|  |                 string_printf(display_str, "Reg:%s", string_get_cstr(country_name)); | ||||||
|  |                 bank_card_set_country_name(bank_card, string_get_cstr(display_str)); | ||||||
|  |             } | ||||||
|  |             string_clear(country_name); | ||||||
|         } |         } | ||||||
|         if(emv_data->currency_code) { |         if(emv_data->currency_code) { | ||||||
|             bank_card_set_currency_name(bank_card, emv_data->currency_code); |             string_t currency_name; | ||||||
|  |             string_init(currency_name); | ||||||
|  |             if(nfc_emv_parser_get_currency_name( | ||||||
|  |                    nfc->dev->storage, emv_data->country_code, currency_name)) { | ||||||
|  |                 string_printf(display_str, "Cur:%s", string_get_cstr(currency_name)); | ||||||
|  |                 bank_card_set_currency_name(bank_card, string_get_cstr(display_str)); | ||||||
|             } |             } | ||||||
|  |             string_clear(currency_name); | ||||||
|  |         } | ||||||
|  |         string_clear(display_str); | ||||||
|     } |     } | ||||||
|     scene_manager_set_scene_state(nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoUid); |     scene_manager_set_scene_state(nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoUid); | ||||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); | ||||||
| @ -136,17 +154,17 @@ bool nfc_scene_device_info_on_event(void* context, SceneManagerEvent event) { | |||||||
|         if((state == NfcSceneDeviceInfoUid) && (event.event == GuiButtonTypeLeft)) { |         if((state == NfcSceneDeviceInfoUid) && (event.event == GuiButtonTypeLeft)) { | ||||||
|             consumed = scene_manager_previous_scene(nfc->scene_manager); |             consumed = scene_manager_previous_scene(nfc->scene_manager); | ||||||
|         } else if((state == NfcSceneDeviceInfoUid) && (event.event == GuiButtonTypeRight)) { |         } else if((state == NfcSceneDeviceInfoUid) && (event.event == GuiButtonTypeRight)) { | ||||||
|             if(nfc->dev.format == NfcDeviceSaveFormatUid) { |             if(nfc->dev->format == NfcDeviceSaveFormatUid) { | ||||||
|                 scene_manager_set_scene_state( |                 scene_manager_set_scene_state( | ||||||
|                     nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoData); |                     nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoData); | ||||||
|                 view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); |                 view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); | ||||||
|                 consumed = true; |                 consumed = true; | ||||||
|             } else if(nfc->dev.format == NfcDeviceSaveFormatMifareUl) { |             } else if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { | ||||||
|                 scene_manager_set_scene_state( |                 scene_manager_set_scene_state( | ||||||
|                     nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoData); |                     nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoData); | ||||||
|                 view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); |                 view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); | ||||||
|                 consumed = true; |                 consumed = true; | ||||||
|             } else if(nfc->dev.format == NfcDeviceSaveFormatBankCard) { |             } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { | ||||||
|                 scene_manager_set_scene_state( |                 scene_manager_set_scene_state( | ||||||
|                     nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoData); |                     nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoData); | ||||||
|                 view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewBankCard); |                 view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewBankCard); | ||||||
| @ -168,7 +186,7 @@ void nfc_scene_device_info_on_exit(void* context) { | |||||||
|     // Clear Custom Widget
 |     // Clear Custom Widget
 | ||||||
|     widget_clear(nfc->widget); |     widget_clear(nfc->widget); | ||||||
| 
 | 
 | ||||||
|     if(nfc->dev.format == NfcDeviceSaveFormatUid) { |     if(nfc->dev->format == NfcDeviceSaveFormatUid) { | ||||||
|         // Clear Dialog
 |         // Clear Dialog
 | ||||||
|         DialogEx* dialog_ex = nfc->dialog_ex; |         DialogEx* dialog_ex = nfc->dialog_ex; | ||||||
|         dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); |         dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||||
| @ -179,11 +197,11 @@ void nfc_scene_device_info_on_exit(void* context) { | |||||||
|         dialog_ex_set_center_button_text(dialog_ex, NULL); |         dialog_ex_set_center_button_text(dialog_ex, NULL); | ||||||
|         dialog_ex_set_result_callback(dialog_ex, NULL); |         dialog_ex_set_result_callback(dialog_ex, NULL); | ||||||
|         dialog_ex_set_context(dialog_ex, NULL); |         dialog_ex_set_context(dialog_ex, NULL); | ||||||
|     } else if(nfc->dev.format == NfcDeviceSaveFormatMifareUl) { |     } else if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { | ||||||
|         // Clear TextBox
 |         // Clear TextBox
 | ||||||
|         text_box_clean(nfc->text_box); |         text_box_clean(nfc->text_box); | ||||||
|         string_clean(nfc->text_box_store); |         string_reset(nfc->text_box_store); | ||||||
|     } else if(nfc->dev.format == NfcDeviceSaveFormatBankCard) { |     } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { | ||||||
|         // Clear Bank Card
 |         // Clear Bank Card
 | ||||||
|         bank_card_clear(nfc->bank_card); |         bank_card_clear(nfc->bank_card); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ void nfc_scene_emulate_apdu_sequence_on_enter(void* context) { | |||||||
|     // Setup and start worker
 |     // Setup and start worker
 | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); | ||||||
|     nfc_worker_start(nfc->worker, NfcWorkerStateEmulateApdu, &nfc->dev.dev_data, NULL, nfc); |     nfc_worker_start(nfc->worker, NfcWorkerStateEmulateApdu, &nfc->dev->dev_data, NULL, nfc); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_scene_emulate_apdu_sequence_on_event(void* context, SceneManagerEvent event) { | bool nfc_scene_emulate_apdu_sequence_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -14,8 +14,8 @@ void nfc_scene_emulate_mifare_ul_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     // Setup view
 |     // Setup view
 | ||||||
|     Popup* popup = nfc->popup; |     Popup* popup = nfc->popup; | ||||||
|     if(strcmp(nfc->dev.dev_name, "")) { |     if(strcmp(nfc->dev->dev_name, "")) { | ||||||
|         nfc_text_store_set(nfc, "%s", nfc->dev.dev_name); |         nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); | ||||||
|     } |     } | ||||||
|     popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); |     popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); | ||||||
|     popup_set_header(popup, "Emulating\nMf Ultralight", 56, 31, AlignLeft, AlignTop); |     popup_set_header(popup, "Emulating\nMf Ultralight", 56, 31, AlignLeft, AlignTop); | ||||||
| @ -25,7 +25,7 @@ void nfc_scene_emulate_mifare_ul_on_enter(void* context) { | |||||||
|     nfc_worker_start( |     nfc_worker_start( | ||||||
|         nfc->worker, |         nfc->worker, | ||||||
|         NfcWorkerStateEmulateMifareUl, |         NfcWorkerStateEmulateMifareUl, | ||||||
|         &nfc->dev.dev_data, |         &nfc->dev->dev_data, | ||||||
|         nfc_emulate_mifare_ul_worker_callback, |         nfc_emulate_mifare_ul_worker_callback, | ||||||
|         nfc); |         nfc); | ||||||
| } | } | ||||||
| @ -45,7 +45,7 @@ bool nfc_scene_emulate_mifare_ul_on_event(void* context, SceneManagerEvent event | |||||||
|            NFC_MF_UL_DATA_CHANGED) { |            NFC_MF_UL_DATA_CHANGED) { | ||||||
|             scene_manager_set_scene_state( |             scene_manager_set_scene_state( | ||||||
|                 nfc->scene_manager, NfcSceneEmulateMifareUl, NFC_MF_UL_DATA_NOT_CHANGED); |                 nfc->scene_manager, NfcSceneEmulateMifareUl, NFC_MF_UL_DATA_NOT_CHANGED); | ||||||
|             nfc_device_save_shadow(&nfc->dev, nfc->dev.dev_name); |             nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); | ||||||
|         } |         } | ||||||
|         consumed = false; |         consumed = false; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -5,10 +5,10 @@ void nfc_scene_emulate_uid_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     // Setup view
 |     // Setup view
 | ||||||
|     Popup* popup = nfc->popup; |     Popup* popup = nfc->popup; | ||||||
|     NfcDeviceCommonData* data = &nfc->dev.dev_data.nfc_data; |     NfcDeviceCommonData* data = &nfc->dev->dev_data.nfc_data; | ||||||
| 
 | 
 | ||||||
|     if(strcmp(nfc->dev.dev_name, "")) { |     if(strcmp(nfc->dev->dev_name, "")) { | ||||||
|         nfc_text_store_set(nfc, "%s", nfc->dev.dev_name); |         nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); | ||||||
|     } else if(data->uid_len == 4) { |     } else if(data->uid_len == 4) { | ||||||
|         nfc_text_store_set( |         nfc_text_store_set( | ||||||
|             nfc, "%02X %02X %02X %02X", data->uid[0], data->uid[1], data->uid[2], data->uid[3]); |             nfc, "%02X %02X %02X %02X", data->uid[0], data->uid[1], data->uid[2], data->uid[3]); | ||||||
| @ -32,7 +32,7 @@ void nfc_scene_emulate_uid_on_enter(void* context) { | |||||||
|     // Setup and start worker
 |     // Setup and start worker
 | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); | ||||||
|     nfc_worker_start(nfc->worker, NfcWorkerStateEmulate, &nfc->dev.dev_data, NULL, nfc); |     nfc_worker_start(nfc->worker, NfcWorkerStateEmulate, &nfc->dev->dev_data, NULL, nfc); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_scene_emulate_uid_on_event(void* context, SceneManagerEvent event) { | bool nfc_scene_emulate_uid_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| void nfc_scene_file_select_on_enter(void* context) { | void nfc_scene_file_select_on_enter(void* context) { | ||||||
|     Nfc* nfc = (Nfc*)context; |     Nfc* nfc = (Nfc*)context; | ||||||
|     // Process file_select return
 |     // Process file_select return
 | ||||||
|     if(nfc_file_select(&nfc->dev)) { |     if(nfc_file_select(nfc->dev)) { | ||||||
|         scene_manager_next_scene(nfc->scene_manager, NfcSceneSavedMenu); |         scene_manager_next_scene(nfc->scene_manager, NfcSceneSavedMenu); | ||||||
|     } else { |     } else { | ||||||
|         scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); |         scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Aleksandr Kutuzov
						Aleksandr Kutuzov