Merge branch 'dev' into release-candidate
This commit is contained in:
		
						commit
						b501d47943
					
				
							
								
								
									
										16
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @ -121,11 +121,11 @@ jobs: | ||||
| 
 | ||||
|       - name: 'Bundle core2 firmware' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         run: | | ||||
|           test -d core2_firmware && rm -rf core2_firmware || true | ||||
|           mkdir core2_firmware | ||||
|           ./scripts/assets.py copro lib/STM32CubeWB core2_firmware STM32WB5x | ||||
|           tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz core2_firmware | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
|           run: | | ||||
|             make -C assets copro_bundle | ||||
|             tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware | ||||
| 
 | ||||
|       - name: 'Upload artifacts to update server' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
| @ -161,7 +161,7 @@ jobs: | ||||
|           comment-id: ${{ steps.fc.outputs.comment-id }} | ||||
|           issue-number: ${{ github.event.pull_request.number }} | ||||
|           body: | | ||||
|             [Click here](https://update.flipperzero.one/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}&target=${{steps.names.outputs.default-target}}) to flash the `${{steps.names.outputs.short-hash}}` version of this branch via WebUSB. | ||||
|             [Click here](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu) for the DFU file to flash the `${{steps.names.outputs.short-hash}}` version of this branch with the [`Install from file` option in qFlipper](https://docs.flipperzero.one/basics/firmware-update). | ||||
|           edit-mode: replace | ||||
| 
 | ||||
|   compact: | ||||
| @ -213,8 +213,8 @@ jobs: | ||||
|         with: | ||||
|           run: | | ||||
|             set -e | ||||
|             make -C assets clean | ||||
|             make -C assets | ||||
|             make assets_rebuild assets_manifest | ||||
|             git diff --quiet || ( echo "Assets recompilation required."; exit 255 ) | ||||
| 
 | ||||
|       - name: 'Build the firmware in docker' | ||||
|         uses: ./.github/actions/docker | ||||
|  | ||||
							
								
								
									
										17
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								Makefile
									
									
									
									
									
								
							| @ -1,8 +1,7 @@ | ||||
| PROJECT_ROOT := $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))) | ||||
| 
 | ||||
| include			$(PROJECT_ROOT)/make/git.mk | ||||
| 
 | ||||
| COPRO_DIR := $(PROJECT_ROOT)/lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x | ||||
| include			$(PROJECT_ROOT)/assets/copro.mk | ||||
| 
 | ||||
| PROJECT_SOURCE_DIRECTORIES := \
 | ||||
| 	$(PROJECT_ROOT)/applications \
 | ||||
| @ -97,7 +96,13 @@ updater_package_bin: firmware_all updater | ||||
| 
 | ||||
| .PHONY: updater_package | ||||
| updater_package: firmware_all updater assets_manifest | ||||
| 	@$(PROJECT_ROOT)/scripts/dist.py copy -t $(TARGET) -p firmware updater -s $(DIST_SUFFIX) -r $(PROJECT_ROOT)/assets/resources --bundlever "$(VERSION_STRING)" | ||||
| 	@$(PROJECT_ROOT)/scripts/dist.py copy \
 | ||||
| 	-t $(TARGET) -p firmware updater \
 | ||||
| 	-s $(DIST_SUFFIX) -r $(PROJECT_ROOT)/assets/resources \
 | ||||
| 	--bundlever "$(VERSION_STRING)" \
 | ||||
| 	--radio $(COPRO_STACK_BIN_PATH) \
 | ||||
| 	--radiotype $(COPRO_STACK_TYPE) \
 | ||||
| 	--obdata $(PROJECT_ROOT)/scripts/ob.data | ||||
| 
 | ||||
| .PHONY: assets_manifest | ||||
| assets_manifest: | ||||
| @ -109,7 +114,7 @@ assets_rebuild: | ||||
| 
 | ||||
| .PHONY: flash_radio | ||||
| flash_radio: | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2radio 0x080D7000 $(COPRO_DIR)/stm32wb5x_BLE_Stack_light_fw.bin | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2radio $(COPRO_STACK_BIN_PATH) --addr=$(COPRO_STACK_ADDR) | ||||
| 	@$(PROJECT_ROOT)/scripts/ob.py set | ||||
| 
 | ||||
| .PHONY: flash_radio_fus | ||||
| @ -125,8 +130,8 @@ flash_radio_fus: | ||||
| 
 | ||||
| .PHONY: 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_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw_for_fus_0_5_3.bin | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw.bin | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_FIRMWARE_DIR)/stm32wb5x_FUS_fw_for_fus_0_5_3.bin | ||||
| 	@$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE $(COPRO_FIRMWARE_DIR)/stm32wb5x_FUS_fw.bin | ||||
| 	@$(PROJECT_ROOT)/scripts/ob.py set | ||||
| 
 | ||||
| .PHONY: lint | ||||
|  | ||||
| @ -32,7 +32,6 @@ void AccessorApp::run(void) { | ||||
| } | ||||
| 
 | ||||
| AccessorApp::AccessorApp() { | ||||
|     furi_hal_power_insomnia_enter(); | ||||
|     notification = static_cast<NotificationApp*>(furi_record_open("notification")); | ||||
|     onewire_host = onewire_host_alloc(); | ||||
|     furi_hal_power_enable_otg(); | ||||
| @ -42,7 +41,6 @@ AccessorApp::~AccessorApp() { | ||||
|     furi_hal_power_disable_otg(); | ||||
|     furi_record_close("notification"); | ||||
|     onewire_host_free(onewire_host); | ||||
|     furi_hal_power_insomnia_exit(); | ||||
| } | ||||
| 
 | ||||
| AccessorAppViewManager* AccessorApp::get_view_manager() { | ||||
| @ -139,4 +137,4 @@ WIEGAND* AccessorApp::get_wiegand() { | ||||
| 
 | ||||
| OneWireHost* AccessorApp::get_one_wire() { | ||||
|     return onewire_host; | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -74,61 +74,113 @@ extern int32_t system_settings_app(void* p); | ||||
| const FlipperApplication FLIPPER_SERVICES[] = { | ||||
| /* Services */ | ||||
| #ifdef SRV_RPC | ||||
|     {.app = rpc_srv, .name = "RpcSrv", .stack_size = 1024 * 4, .icon = NULL}, | ||||
|     {.app = rpc_srv, | ||||
|      .name = "RpcSrv", | ||||
|      .stack_size = 1024 * 4, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_BT | ||||
|     {.app = bt_srv, .name = "BtSrv", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = bt_srv, | ||||
|      .name = "BtSrv", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_CLI | ||||
|     {.app = cli_srv, .name = "CliSrv", .stack_size = 4096, .icon = NULL}, | ||||
|     {.app = cli_srv, | ||||
|      .name = "CliSrv", | ||||
|      .stack_size = 4096, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_DIALOGS | ||||
|     {.app = dialogs_srv, .name = "DialogsSrv", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = dialogs_srv, | ||||
|      .name = "DialogsSrv", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_DOLPHIN | ||||
|     {.app = dolphin_srv, .name = "DolphinSrv", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = dolphin_srv, | ||||
|      .name = "DolphinSrv", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_DESKTOP | ||||
| #ifdef SRV_UPDATER | ||||
| #error SRV_UPDATER and SRV_DESKTOP are mutually exclusive! | ||||
| #endif | ||||
|     {.app = desktop_srv, .name = "DesktopSrv", .stack_size = 2048, .icon = NULL}, | ||||
|     {.app = desktop_srv, | ||||
|      .name = "DesktopSrv", | ||||
|      .stack_size = 2048, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_GUI | ||||
|     {.app = gui_srv, .name = "GuiSrv", .stack_size = 2048, .icon = NULL}, | ||||
|     {.app = gui_srv, | ||||
|      .name = "GuiSrv", | ||||
|      .stack_size = 2048, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_INPUT | ||||
|     {.app = input_srv, .name = "InputSrv", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = input_srv, | ||||
|      .name = "InputSrv", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_LOADER | ||||
|     {.app = loader_srv, .name = "LoaderSrv", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = loader_srv, | ||||
|      .name = "LoaderSrv", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_NOTIFICATION | ||||
|     {.app = notification_srv, .name = "NotificationSrv", .stack_size = 1536, .icon = NULL}, | ||||
|     {.app = notification_srv, | ||||
|      .name = "NotificationSrv", | ||||
|      .stack_size = 1536, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_POWER | ||||
|     {.app = power_srv, .name = "PowerSrv", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = power_srv, | ||||
|      .name = "PowerSrv", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_STORAGE | ||||
|     {.app = storage_srv, .name = "StorageSrv", .stack_size = 3072, .icon = NULL}, | ||||
|     {.app = storage_srv, | ||||
|      .name = "StorageSrv", | ||||
|      .stack_size = 3072, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_UPDATER | ||||
| #ifdef SRV_DESKTOP | ||||
| #error SRV_UPDATER and SRV_DESKTOP are mutually exclusive! | ||||
| #endif | ||||
|     {.app = updater_srv, .name = "UpdaterSrv", .stack_size = 2048, .icon = NULL}, | ||||
|     {.app = updater_srv, | ||||
|      .name = "UpdaterSrv", | ||||
|      .stack_size = 2048, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| @ -139,7 +191,11 @@ const FlipperApplication FLIPPER_SYSTEM_APPS[] = { | ||||
| #ifdef SRV_UPDATER | ||||
| #error APP_UPDATER and SRV_UPDATER are mutually exclusive! | ||||
| #endif | ||||
|     {.app = updater_srv, .name = "UpdaterApp", .stack_size = 2048, .icon = NULL}, | ||||
|     {.app = updater_srv, | ||||
|      .name = "UpdaterApp", | ||||
|      .stack_size = 2048, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| @ -149,35 +205,67 @@ const size_t FLIPPER_SYSTEM_APPS_COUNT = COUNT_OF(FLIPPER_SYSTEM_APPS); | ||||
| const FlipperApplication FLIPPER_APPS[] = { | ||||
| 
 | ||||
| #ifdef APP_SUBGHZ | ||||
|     {.app = subghz_app, .name = "Sub-GHz", .stack_size = 2048, .icon = &A_Sub1ghz_14}, | ||||
|     {.app = subghz_app, | ||||
|      .name = "Sub-GHz", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_Sub1ghz_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_LF_RFID | ||||
|     {.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 2048, .icon = &A_125khz_14}, | ||||
|     {.app = lfrfid_app, | ||||
|      .name = "125 kHz RFID", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_125khz_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_NFC | ||||
|     {.app = nfc_app, .name = "NFC", .stack_size = 4096, .icon = &A_NFC_14}, | ||||
|     {.app = nfc_app, | ||||
|      .name = "NFC", | ||||
|      .stack_size = 4096, | ||||
|      .icon = &A_NFC_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_INFRARED | ||||
|     {.app = infrared_app, .name = "Infrared", .stack_size = 1024 * 3, .icon = &A_Infrared_14}, | ||||
|     {.app = infrared_app, | ||||
|      .name = "Infrared", | ||||
|      .stack_size = 1024 * 3, | ||||
|      .icon = &A_Infrared_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_GPIO | ||||
|     {.app = gpio_app, .name = "GPIO", .stack_size = 1024, .icon = &A_GPIO_14}, | ||||
|     {.app = gpio_app, | ||||
|      .name = "GPIO", | ||||
|      .stack_size = 1024, | ||||
|      .icon = &A_GPIO_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_IBUTTON | ||||
|     {.app = ibutton_app, .name = "iButton", .stack_size = 2048, .icon = &A_iButton_14}, | ||||
|     {.app = ibutton_app, | ||||
|      .name = "iButton", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_iButton_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_BAD_USB | ||||
|     {.app = bad_usb_app, .name = "Bad USB", .stack_size = 2048, .icon = &A_BadUsb_14}, | ||||
|     {.app = bad_usb_app, | ||||
|      .name = "Bad USB", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_BadUsb_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_U2F | ||||
|     {.app = u2f_app, .name = "U2F", .stack_size = 2048, .icon = &A_U2F_14}, | ||||
|     {.app = u2f_app, | ||||
|      .name = "U2F", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_U2F_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| }; | ||||
| @ -234,15 +322,27 @@ const size_t FLIPPER_ON_SYSTEM_START_COUNT = COUNT_OF(FLIPPER_ON_SYSTEM_START); | ||||
| // Plugin menu
 | ||||
| const FlipperApplication FLIPPER_PLUGINS[] = { | ||||
| #ifdef APP_BLE_HID | ||||
|     {.app = bt_hid_app, .name = "Bluetooth Remote", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = bt_hid_app, | ||||
|      .name = "Bluetooth Remote", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_MUSIC_PLAYER | ||||
|     {.app = music_player_app, .name = "Music Player", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
|     {.app = music_player_app, | ||||
|      .name = "Music Player", | ||||
|      .stack_size = 1024, | ||||
|      .icon = &A_Plugins_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_SNAKE_GAME | ||||
|     {.app = snake_game_app, .name = "Snake Game", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
|     {.app = snake_game_app, | ||||
|      .name = "Snake Game", | ||||
|      .stack_size = 1024, | ||||
|      .icon = &A_Plugins_14, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| @ -251,108 +351,201 @@ const size_t FLIPPER_PLUGINS_COUNT = COUNT_OF(FLIPPER_PLUGINS); | ||||
| // Plugin menu
 | ||||
| const FlipperApplication FLIPPER_DEBUG_APPS[] = { | ||||
| #ifdef APP_BLINK | ||||
|     {.app = blink_test_app, .name = "Blink Test", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = blink_test_app, | ||||
|      .name = "Blink Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_VIBRO_TEST | ||||
|     {.app = vibro_test_app, .name = "Vibro Test", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = vibro_test_app, | ||||
|      .name = "Vibro Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_KEYPAD_TEST | ||||
|     {.app = keypad_test_app, .name = "Keypad Test", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = keypad_test_app, | ||||
|      .name = "Keypad Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_ACCESSOR | ||||
|     {.app = accessor_app, .name = "Accessor", .stack_size = 4096, .icon = NULL}, | ||||
|     {.app = accessor_app, | ||||
|      .name = "Accessor", | ||||
|      .stack_size = 4096, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_USB_TEST | ||||
|     {.app = usb_test_app, .name = "USB Test", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = usb_test_app, | ||||
|      .name = "USB Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_USB_MOUSE | ||||
|     {.app = usb_mouse_app, .name = "USB Mouse Demo", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = usb_mouse_app, | ||||
|      .name = "USB Mouse Demo", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_UART_ECHO | ||||
|     {.app = uart_echo_app, .name = "Uart Echo", .stack_size = 2048, .icon = NULL}, | ||||
|     {.app = uart_echo_app, | ||||
|      .name = "Uart Echo", | ||||
|      .stack_size = 2048, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_INFRARED_MONITOR | ||||
|     {.app = infrared_monitor_app, .name = "Infrared Monitor", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = infrared_monitor_app, | ||||
|      .name = "Infrared Monitor", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_SCENED | ||||
|     {.app = scened_app, .name = "Templated Scene", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = scened_app, | ||||
|      .name = "Templated Scene", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_LF_RFID | ||||
|     {.app = lfrfid_debug_app, .name = "LF-RFID Debug", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = lfrfid_debug_app, | ||||
|      .name = "LF-RFID Debug", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_BT | ||||
|     {.app = bt_debug_app, .name = "Bluetooth Debug", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = bt_debug_app, | ||||
|      .name = "Bluetooth Debug", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_UNIT_TESTS | ||||
|     {.app = delay_test_app, .name = "Delay Test", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = delay_test_app, | ||||
|      .name = "Delay Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_DISPLAY_TEST | ||||
|     {.app = display_test_app, .name = "Display Test", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = display_test_app, | ||||
|      .name = "Display Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_BATTERY_TEST | ||||
|     {.app = battery_test_app, .name = "Battery Test", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = battery_test_app, | ||||
|      .name = "Battery Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_TEXT_BOX_TEST | ||||
|     {.app = text_box_test_app, .name = "Text Box Test", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = text_box_test_app, | ||||
|      .name = "Text Box Test", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| const size_t FLIPPER_DEBUG_APPS_COUNT = COUNT_OF(FLIPPER_DEBUG_APPS); | ||||
| 
 | ||||
| #ifdef APP_ARCHIVE | ||||
| const FlipperApplication FLIPPER_ARCHIVE = | ||||
|     {.app = archive_app, .name = "Archive", .stack_size = 4096, .icon = &A_FileManager_14}; | ||||
| const FlipperApplication FLIPPER_ARCHIVE = { | ||||
|     .app = archive_app, | ||||
|     .name = "Archive", | ||||
|     .stack_size = 4096, | ||||
|     .icon = &A_FileManager_14, | ||||
|     .flags = FlipperApplicationFlagDefault}; | ||||
| #endif | ||||
| 
 | ||||
| // Settings menu
 | ||||
| const FlipperApplication FLIPPER_SETTINGS_APPS[] = { | ||||
| #ifdef SRV_BT | ||||
|     {.app = bt_settings_app, .name = "Bluetooth", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = bt_settings_app, | ||||
|      .name = "Bluetooth", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_NOTIFICATION | ||||
|     {.app = notification_settings_app, | ||||
|      .name = "LCD and Notifications", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL}, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_STORAGE | ||||
|     {.app = storage_settings_app, .name = "Storage", .stack_size = 2048, .icon = NULL}, | ||||
|     {.app = storage_settings_app, | ||||
|      .name = "Storage", | ||||
|      .stack_size = 2048, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_POWER | ||||
|     {.app = power_settings_app, .name = "Power", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = power_settings_app, | ||||
|      .name = "Power", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagInsomniaSafe}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_DESKTOP | ||||
|     {.app = desktop_settings_app, .name = "Desktop", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = desktop_settings_app, | ||||
|      .name = "Desktop", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_PASSPORT | ||||
|     {.app = passport_app, .name = "Passport", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = passport_app, | ||||
|      .name = "Passport", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_GUI | ||||
|     {.app = system_settings_app, .name = "System", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = system_settings_app, | ||||
|      .name = "System", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_ABOUT | ||||
|     {.app = about_settings_app, .name = "About", .stack_size = 1024, .icon = NULL}, | ||||
|     {.app = about_settings_app, | ||||
|      .name = "About", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -3,11 +3,17 @@ | ||||
| #include <furi.h> | ||||
| #include <gui/icon.h> | ||||
| 
 | ||||
| typedef enum { | ||||
|     FlipperApplicationFlagDefault = 0, | ||||
|     FlipperApplicationFlagInsomniaSafe = (1 << 0), | ||||
| } FlipperApplicationFlag; | ||||
| 
 | ||||
| typedef struct { | ||||
|     const FuriThreadCallback app; | ||||
|     const char* name; | ||||
|     const size_t stack_size; | ||||
|     const Icon* icon; | ||||
|     const FlipperApplicationFlag flags; | ||||
| } FlipperApplication; | ||||
| 
 | ||||
| typedef void (*FlipperOnStartHook)(void); | ||||
|  | ||||
| @ -27,6 +27,7 @@ struct ArchiveApp { | ||||
|     ArchiveBrowserView* browser; | ||||
|     TextInput* text_input; | ||||
|     Widget* widget; | ||||
|     FuriPubSubSubscription* loader_stop_subscription; | ||||
|     char text_store[MAX_NAME_LEN]; | ||||
|     char file_extension[MAX_EXT_LEN + 1]; | ||||
| }; | ||||
|  | ||||
| @ -77,6 +77,7 @@ void archive_set_item_count(ArchiveBrowserView* browser, uint32_t count) { | ||||
|     with_view_model( | ||||
|         browser->view, (ArchiveBrowserViewModel * model) { | ||||
|             model->item_cnt = count; | ||||
|             model->item_idx = CLAMP(model->item_idx, model->item_cnt - 1, 0); | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
| @ -397,15 +398,17 @@ void archive_enter_dir(ArchiveBrowserView* browser, string_t name) { | ||||
| 
 | ||||
|     archive_dir_count_items(browser, string_get_cstr(name)); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         browser->view, (ArchiveBrowserViewModel * model) { | ||||
|             idx_last_array_push_back(model->idx_last, model->item_idx); | ||||
|             model->array_offset = 0; | ||||
|             model->item_idx = 0; | ||||
|             return false; | ||||
|         }); | ||||
|     if(string_cmp(browser->path, name) != 0) { | ||||
|         with_view_model( | ||||
|             browser->view, (ArchiveBrowserViewModel * model) { | ||||
|                 idx_last_array_push_back(model->idx_last, model->item_idx); | ||||
|                 model->array_offset = 0; | ||||
|                 model->item_idx = 0; | ||||
|                 return false; | ||||
|             }); | ||||
| 
 | ||||
|     string_set(browser->path, name); | ||||
|         string_set(browser->path, name); | ||||
|     } | ||||
| 
 | ||||
|     archive_switch_dir(browser, string_get_cstr(browser->path)); | ||||
| } | ||||
|  | ||||
| @ -18,6 +18,18 @@ static const char* flipper_app_name[] = { | ||||
|     [ArchiveFileTypeUpdateManifest] = "UpdaterApp", | ||||
| }; | ||||
| 
 | ||||
| static void archive_loader_callback(const void* message, void* context) { | ||||
|     furi_assert(message); | ||||
|     furi_assert(context); | ||||
|     const LoaderEvent* event = message; | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
| 
 | ||||
|     if(event->type == LoaderEventTypeApplicationStopped) { | ||||
|         view_dispatcher_send_custom_event( | ||||
|             archive->view_dispatcher, ArchiveBrowserEventLoaderAppExit); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selected) { | ||||
|     Loader* loader = furi_record_open("loader"); | ||||
| 
 | ||||
| @ -52,6 +64,11 @@ void archive_scene_browser_on_enter(void* context) { | ||||
|     archive_browser_set_callback(browser, archive_scene_browser_callback, archive); | ||||
|     archive_update_focus(browser, archive->text_store); | ||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewBrowser); | ||||
| 
 | ||||
|     Loader* loader = furi_record_open("loader"); | ||||
|     archive->loader_stop_subscription = | ||||
|         furi_pubsub_subscribe(loader_get_pubsub(loader), archive_loader_callback, archive); | ||||
|     furi_record_close("loader"); | ||||
| } | ||||
| 
 | ||||
| bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { | ||||
| @ -147,11 +164,25 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { | ||||
|             archive_file_array_load(archive->browser, 1); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case ArchiveBrowserEventLoaderAppExit: | ||||
|             if(!favorites) { | ||||
|                 archive_enter_dir(browser, browser->path); | ||||
|             } else { | ||||
|                 archive_favorites_read(browser); | ||||
|             } | ||||
| 
 | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         case ArchiveBrowserEventExit: | ||||
|             if(archive_get_depth(browser)) { | ||||
|                 archive_leave_dir(browser); | ||||
|             } else { | ||||
|                 Loader* loader = furi_record_open("loader"); | ||||
|                 furi_pubsub_unsubscribe( | ||||
|                     loader_get_pubsub(loader), archive->loader_stop_subscription); | ||||
|                 furi_record_close("loader"); | ||||
| 
 | ||||
|                 view_dispatcher_stop(archive->view_dispatcher); | ||||
|             } | ||||
|             consumed = true; | ||||
| @ -165,5 +196,9 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { | ||||
| } | ||||
| 
 | ||||
| void archive_scene_browser_on_exit(void* context) { | ||||
|     // ArchiveApp* archive = (ArchiveApp*)context;
 | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
| 
 | ||||
|     Loader* loader = furi_record_open("loader"); | ||||
|     furi_pubsub_unsubscribe(loader_get_pubsub(loader), archive->loader_stop_subscription); | ||||
|     furi_record_close("loader"); | ||||
| } | ||||
|  | ||||
| @ -48,6 +48,8 @@ typedef enum { | ||||
|     ArchiveBrowserEventLoadPrevItems, | ||||
|     ArchiveBrowserEventLoadNextItems, | ||||
| 
 | ||||
|     ArchiveBrowserEventLoaderAppExit, | ||||
| 
 | ||||
|     ArchiveBrowserEventExit, | ||||
| } ArchiveBrowserEvent; | ||||
| 
 | ||||
| @ -72,7 +74,6 @@ struct ArchiveBrowserView { | ||||
|     View* view; | ||||
|     ArchiveBrowserViewCallback callback; | ||||
|     void* context; | ||||
| 
 | ||||
|     string_t path; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -148,7 +148,7 @@ static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { | ||||
| static void bt_cli_scan_callback(GapAddress address, void* context) { | ||||
|     furi_assert(context); | ||||
|     osMessageQueueId_t queue = context; | ||||
|     osMessageQueuePut(queue, &address, NULL, 250); | ||||
|     osMessageQueuePut(queue, &address, 0, 250); | ||||
| } | ||||
| 
 | ||||
| static void bt_cli_command_scan(Cli* cli, string_t args, void* context) { | ||||
|  | ||||
| @ -60,7 +60,7 @@ static ViewPort* bt_pin_code_view_port_alloc(Bt* bt) { | ||||
| 
 | ||||
| static void bt_pin_code_show(Bt* bt, uint32_t pin_code) { | ||||
|     bt->pin_code = pin_code; | ||||
|     notification_message(bt->notification, &sequence_display_on); | ||||
|     notification_message(bt->notification, &sequence_display_backlight_on); | ||||
|     gui_view_port_send_to_front(bt->gui, bt->pin_code_view_port); | ||||
|     view_port_enabled_set(bt->pin_code_view_port, true); | ||||
| } | ||||
| @ -74,7 +74,7 @@ static void bt_pin_code_hide(Bt* bt) { | ||||
| 
 | ||||
| static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { | ||||
|     furi_assert(bt); | ||||
|     notification_message(bt->notification, &sequence_display_on); | ||||
|     notification_message(bt->notification, &sequence_display_backlight_on); | ||||
|     string_t pin_str; | ||||
|     dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0); | ||||
|     string_init_printf(pin_str, "Verify code\n%06d", pin); | ||||
| @ -319,6 +319,13 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { | ||||
| int32_t bt_srv() { | ||||
|     Bt* bt = bt_alloc(); | ||||
| 
 | ||||
|     if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { | ||||
|         FURI_LOG_W(TAG, "Skipped BT init: device in special startup mode"); | ||||
|         ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT); | ||||
|         furi_record_create("bt", bt); | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     // Read keys
 | ||||
|     if(!bt_keys_storage_load(bt)) { | ||||
|         FURI_LOG_W(TAG, "Failed to load bonding keys"); | ||||
|  | ||||
| @ -1,9 +1,11 @@ | ||||
| #include "cli_i.h" | ||||
| #include "cli_commands.h" | ||||
| 
 | ||||
| #include "cli_vcp.h" | ||||
| #include <furi_hal_version.h> | ||||
| #include <loader/loader.h> | ||||
| 
 | ||||
| #define TAG "CliSrv" | ||||
| 
 | ||||
| Cli* cli_alloc() { | ||||
|     Cli* cli = malloc(sizeof(Cli)); | ||||
| 
 | ||||
| @ -12,55 +14,78 @@ Cli* cli_alloc() { | ||||
|     string_init(cli->last_line); | ||||
|     string_init(cli->line); | ||||
| 
 | ||||
|     cli->session = NULL; | ||||
| 
 | ||||
|     cli->mutex = osMutexNew(NULL); | ||||
|     furi_check(cli->mutex); | ||||
| 
 | ||||
|     cli->idle_sem = osSemaphoreNew(1, 0, NULL); | ||||
| 
 | ||||
|     return cli; | ||||
| } | ||||
| 
 | ||||
| void cli_free(Cli* cli) { | ||||
| void cli_putc(Cli* cli, char c) { | ||||
|     furi_assert(cli); | ||||
| 
 | ||||
|     string_clear(cli->last_line); | ||||
|     string_clear(cli->line); | ||||
| 
 | ||||
|     CliCommandTree_clear(cli->commands); | ||||
| 
 | ||||
|     free(cli); | ||||
| } | ||||
| 
 | ||||
| void cli_putc(char c) { | ||||
|     furi_hal_vcp_tx((uint8_t*)&c, 1); | ||||
|     if(cli->session != NULL) { | ||||
|         cli->session->tx((uint8_t*)&c, 1); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| char cli_getc(Cli* cli) { | ||||
|     furi_assert(cli); | ||||
|     char c; | ||||
|     if(furi_hal_vcp_rx((uint8_t*)&c, 1) == 0) { | ||||
|     char c = 0; | ||||
|     if(cli->session != NULL) { | ||||
|         if(cli->session->rx((uint8_t*)&c, 1, osWaitForever) == 0) { | ||||
|             cli_reset(cli); | ||||
|         } | ||||
|     } else { | ||||
|         cli_reset(cli); | ||||
|     } | ||||
|     return c; | ||||
| } | ||||
| 
 | ||||
| void cli_stdout_callback(void* _cookie, const char* data, size_t size) { | ||||
|     furi_hal_vcp_tx((const uint8_t*)data, size); | ||||
| } | ||||
| 
 | ||||
| void cli_write(Cli* cli, const uint8_t* buffer, size_t size) { | ||||
|     return furi_hal_vcp_tx(buffer, size); | ||||
|     furi_assert(cli); | ||||
|     if(cli->session != NULL) { | ||||
|         cli->session->tx(buffer, size); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| size_t cli_read(Cli* cli, uint8_t* buffer, size_t size) { | ||||
|     return furi_hal_vcp_rx(buffer, size); | ||||
|     furi_assert(cli); | ||||
|     if(cli->session != NULL) { | ||||
|         return cli->session->rx(buffer, size, osWaitForever); | ||||
|     } else { | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout) { | ||||
|     furi_assert(cli); | ||||
|     if(cli->session != NULL) { | ||||
|         return cli->session->rx(buffer, size, timeout); | ||||
|     } else { | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool cli_cmd_interrupt_received(Cli* cli) { | ||||
|     furi_assert(cli); | ||||
|     char c = '\0'; | ||||
|     if(furi_hal_vcp_rx_with_timeout((uint8_t*)&c, 1, 0) == 1) { | ||||
|         return c == CliSymbolAsciiETX; | ||||
|     } else { | ||||
|         return false; | ||||
|     if(cli->session != NULL) { | ||||
|         if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) { | ||||
|             return c == CliSymbolAsciiETX; | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool cli_is_connected(Cli* cli) { | ||||
|     furi_assert(cli); | ||||
|     if(cli->session != NULL) { | ||||
|         return (cli->session->is_connected()); | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void cli_print_usage(const char* cmd, const char* usage, const char* arg) { | ||||
| @ -139,7 +164,7 @@ static void cli_handle_backspace(Cli* cli) { | ||||
| 
 | ||||
|         cli->cursor_position--; | ||||
|     } else { | ||||
|         cli_putc(CliSymbolAsciiBell); | ||||
|         cli_putc(cli, CliSymbolAsciiBell); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -210,7 +235,7 @@ static void cli_handle_enter(Cli* cli) { | ||||
|         printf( | ||||
|             "`%s` command not found, use `help` or `?` to list all available commands", | ||||
|             string_get_cstr(command)); | ||||
|         cli_putc(CliSymbolAsciiBell); | ||||
|         cli_putc(cli, CliSymbolAsciiBell); | ||||
|     } | ||||
|     furi_check(osMutexRelease(cli->mutex) == osOK); | ||||
| 
 | ||||
| @ -301,43 +326,43 @@ static void cli_handle_escape(Cli* cli, char c) { | ||||
| } | ||||
| 
 | ||||
| void cli_process_input(Cli* cli) { | ||||
|     char c = cli_getc(cli); | ||||
|     size_t r; | ||||
|     char in_chr = cli_getc(cli); | ||||
|     size_t rx_len; | ||||
| 
 | ||||
|     if(c == CliSymbolAsciiTab) { | ||||
|     if(in_chr == CliSymbolAsciiTab) { | ||||
|         cli_handle_autocomplete(cli); | ||||
|     } else if(c == CliSymbolAsciiSOH) { | ||||
|     } else if(in_chr == CliSymbolAsciiSOH) { | ||||
|         osDelay(33); // We are too fast, Minicom is not ready yet
 | ||||
|         cli_motd(); | ||||
|         cli_prompt(cli); | ||||
|     } else if(c == CliSymbolAsciiETX) { | ||||
|     } else if(in_chr == CliSymbolAsciiETX) { | ||||
|         cli_reset(cli); | ||||
|         cli_prompt(cli); | ||||
|     } else if(c == CliSymbolAsciiEOT) { | ||||
|     } else if(in_chr == CliSymbolAsciiEOT) { | ||||
|         cli_reset(cli); | ||||
|     } else if(c == CliSymbolAsciiEsc) { | ||||
|         r = furi_hal_vcp_rx((uint8_t*)&c, 1); | ||||
|         if(r && c == '[') { | ||||
|             furi_hal_vcp_rx((uint8_t*)&c, 1); | ||||
|             cli_handle_escape(cli, c); | ||||
|     } else if(in_chr == CliSymbolAsciiEsc) { | ||||
|         rx_len = cli_read(cli, (uint8_t*)&in_chr, 1); | ||||
|         if((rx_len > 0) && (in_chr == '[')) { | ||||
|             cli_read(cli, (uint8_t*)&in_chr, 1); | ||||
|             cli_handle_escape(cli, in_chr); | ||||
|         } else { | ||||
|             cli_putc(CliSymbolAsciiBell); | ||||
|             cli_putc(cli, CliSymbolAsciiBell); | ||||
|         } | ||||
|     } else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) { | ||||
|     } else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) { | ||||
|         cli_handle_backspace(cli); | ||||
|     } else if(c == CliSymbolAsciiCR) { | ||||
|     } else if(in_chr == CliSymbolAsciiCR) { | ||||
|         cli_handle_enter(cli); | ||||
|     } else if(c >= 0x20 && c < 0x7F) { | ||||
|     } else if(in_chr >= 0x20 && in_chr < 0x7F) { | ||||
|         if(cli->cursor_position == string_size(cli->line)) { | ||||
|             string_push_back(cli->line, c); | ||||
|             cli_putc(c); | ||||
|             string_push_back(cli->line, in_chr); | ||||
|             cli_putc(cli, in_chr); | ||||
|         } else { | ||||
|             // ToDo: better way?
 | ||||
|             string_t temp; | ||||
|             string_init(temp); | ||||
|             string_reserve(temp, string_size(cli->line) + 1); | ||||
|             string_set_strn(temp, string_get_cstr(cli->line), cli->cursor_position); | ||||
|             string_push_back(temp, c); | ||||
|             string_push_back(temp, in_chr); | ||||
|             string_cat_str(temp, string_get_cstr(cli->line) + cli->cursor_position); | ||||
| 
 | ||||
|             // cli->line is cleared and temp's buffer moved to cli->line
 | ||||
| @ -345,12 +370,12 @@ void cli_process_input(Cli* cli) { | ||||
|             // NO MEMORY LEAK, STOP REPORTING IT
 | ||||
| 
 | ||||
|             // Print character in replace mode
 | ||||
|             printf("\e[4h%c\e[4l", c); | ||||
|             printf("\e[4h%c\e[4l", in_chr); | ||||
|             fflush(stdout); | ||||
|         } | ||||
|         cli->cursor_position++; | ||||
|     } else { | ||||
|         cli_putc(CliSymbolAsciiBell); | ||||
|         cli_putc(cli, CliSymbolAsciiBell); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -398,19 +423,59 @@ void cli_delete_command(Cli* cli, const char* name) { | ||||
|     string_clear(name_str); | ||||
| } | ||||
| 
 | ||||
| void cli_session_open(Cli* cli, void* session) { | ||||
|     furi_assert(cli); | ||||
| 
 | ||||
|     furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK); | ||||
|     cli->session = session; | ||||
|     if(cli->session != NULL) { | ||||
|         cli->session->init(); | ||||
|         furi_stdglue_set_thread_stdout_callback(cli->session->tx_stdout); | ||||
|     } else { | ||||
|         furi_stdglue_set_thread_stdout_callback(NULL); | ||||
|     } | ||||
|     osSemaphoreRelease(cli->idle_sem); | ||||
|     furi_check(osMutexRelease(cli->mutex) == osOK); | ||||
| } | ||||
| 
 | ||||
| void cli_session_close(Cli* cli) { | ||||
|     furi_assert(cli); | ||||
| 
 | ||||
|     furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK); | ||||
|     if(cli->session != NULL) { | ||||
|         cli->session->deinit(); | ||||
|     } | ||||
|     cli->session = NULL; | ||||
|     furi_stdglue_set_thread_stdout_callback(NULL); | ||||
|     furi_check(osMutexRelease(cli->mutex) == osOK); | ||||
| } | ||||
| 
 | ||||
| int32_t cli_srv(void* p) { | ||||
|     Cli* cli = cli_alloc(); | ||||
| 
 | ||||
|     furi_hal_vcp_init(); | ||||
| 
 | ||||
|     // Init basic cli commands
 | ||||
|     cli_commands_init(cli); | ||||
| 
 | ||||
|     furi_record_create("cli", cli); | ||||
| 
 | ||||
|     furi_stdglue_set_thread_stdout_callback(cli_stdout_callback); | ||||
|     if(cli->session != NULL) { | ||||
|         furi_stdglue_set_thread_stdout_callback(cli->session->tx_stdout); | ||||
|     } else { | ||||
|         furi_stdglue_set_thread_stdout_callback(NULL); | ||||
|     } | ||||
| 
 | ||||
|     if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { | ||||
|         cli_session_open(cli, &cli_vcp); | ||||
|     } else { | ||||
|         FURI_LOG_W(TAG, "Skipped CLI session open: device in special startup mode"); | ||||
|     } | ||||
| 
 | ||||
|     while(1) { | ||||
|         cli_process_input(cli); | ||||
|         if(cli->session != NULL) { | ||||
|             cli_process_input(cli); | ||||
|         } else { | ||||
|             furi_check(osSemaphoreAcquire(cli->idle_sem, osWaitForever) == osOK); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
|  | ||||
| @ -73,17 +73,28 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg); | ||||
|  */ | ||||
| void cli_delete_command(Cli* cli, const char* name); | ||||
| 
 | ||||
| /** Read from terminal Do it only from inside of cli call.
 | ||||
| /** Read from terminal
 | ||||
|  * | ||||
|  * @param      cli     Cli instance | ||||
|  * @param      buffer  pointer to buffer | ||||
|  * @param      size    size of buffer in bytes | ||||
|  * | ||||
|  * @return     bytes written | ||||
|  * @return     bytes read | ||||
|  */ | ||||
| size_t cli_read(Cli* cli, uint8_t* buffer, size_t size); | ||||
| 
 | ||||
| /** Not blocking check for interrupt command received
 | ||||
| /** Non-blocking read from terminal
 | ||||
|  * | ||||
|  * @param      cli     Cli instance | ||||
|  * @param      buffer  pointer to buffer | ||||
|  * @param      size    size of buffer in bytes | ||||
|  * @param      timeout timeout value in ms | ||||
|  * | ||||
|  * @return     bytes read | ||||
|  */ | ||||
| size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout); | ||||
| 
 | ||||
| /** Non-blocking check for interrupt command received
 | ||||
|  * | ||||
|  * @param      cli   Cli instance | ||||
|  * | ||||
| @ -111,6 +122,12 @@ char cli_getc(Cli* cli); | ||||
|  */ | ||||
| void cli_nl(); | ||||
| 
 | ||||
| void cli_session_open(Cli* cli, void* session); | ||||
| 
 | ||||
| void cli_session_close(Cli* cli); | ||||
| 
 | ||||
| bool cli_is_connected(Cli* cli); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @ -202,7 +202,7 @@ void cli_command_led(Cli* cli, string_t args, void* context) { | ||||
|     } else if(!string_cmp(light_name, "b")) { | ||||
|         notification_led_message.type = NotificationMessageTypeLedBlue; | ||||
|     } else if(!string_cmp(light_name, "bl")) { | ||||
|         notification_led_message.type = NotificationMessageTypeLedDisplay; | ||||
|         notification_led_message.type = NotificationMessageTypeLedDisplayBacklight; | ||||
|     } else { | ||||
|         cli_print_usage("led", "<r|g|b|bl> <0-255>", string_get_cstr(args)); | ||||
|         string_clear(light_name); | ||||
|  | ||||
| @ -18,6 +18,17 @@ typedef struct { | ||||
|     uint32_t flags; | ||||
| } CliCommand; | ||||
| 
 | ||||
| typedef struct CliSession CliSession; | ||||
| 
 | ||||
| struct CliSession { | ||||
|     void (*init)(void); | ||||
|     void (*deinit)(void); | ||||
|     size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout); | ||||
|     void (*tx)(const uint8_t* buffer, size_t size); | ||||
|     void (*tx_stdout)(void* _cookie, const char* data, size_t size); | ||||
|     bool (*is_connected)(void); | ||||
| }; | ||||
| 
 | ||||
| BPTREE_DEF2( | ||||
|     CliCommandTree, | ||||
|     CLI_COMMANDS_TREE_RANK, | ||||
| @ -31,18 +42,18 @@ BPTREE_DEF2( | ||||
| struct Cli { | ||||
|     CliCommandTree_t commands; | ||||
|     osMutexId_t mutex; | ||||
|     osSemaphoreId_t idle_sem; | ||||
|     string_t last_line; | ||||
|     string_t line; | ||||
|     CliSession* session; | ||||
| 
 | ||||
|     size_t cursor_position; | ||||
| }; | ||||
| 
 | ||||
| Cli* cli_alloc(); | ||||
| 
 | ||||
| void cli_free(Cli* cli); | ||||
| 
 | ||||
| void cli_reset(Cli* cli); | ||||
| 
 | ||||
| void cli_putc(char c); | ||||
| void cli_putc(Cli* cli, char c); | ||||
| 
 | ||||
| void cli_stdout_callback(void* _cookie, const char* data, size_t size); | ||||
|  | ||||
| @ -2,8 +2,9 @@ | ||||
| #include <furi_hal.h> | ||||
| #include <furi.h> | ||||
| #include <stream_buffer.h> | ||||
| #include "cli_i.h" | ||||
| 
 | ||||
| #define TAG "FuriHalVcp" | ||||
| #define TAG "CliVcp" | ||||
| 
 | ||||
| #define USB_CDC_PKT_LEN CDC_DATA_SZ | ||||
| #define VCP_RX_BUF_SIZE (USB_CDC_PKT_LEN * 3) | ||||
| @ -12,19 +13,18 @@ | ||||
| #define VCP_IF_NUM 0 | ||||
| 
 | ||||
| typedef enum { | ||||
|     VcpEvtEnable = (1 << 0), | ||||
|     VcpEvtDisable = (1 << 1), | ||||
|     VcpEvtConnect = (1 << 2), | ||||
|     VcpEvtDisconnect = (1 << 3), | ||||
|     VcpEvtStreamRx = (1 << 4), | ||||
|     VcpEvtRx = (1 << 5), | ||||
|     VcpEvtStreamTx = (1 << 6), | ||||
|     VcpEvtTx = (1 << 7), | ||||
|     VcpEvtStop = (1 << 0), | ||||
|     VcpEvtConnect = (1 << 1), | ||||
|     VcpEvtDisconnect = (1 << 2), | ||||
|     VcpEvtStreamRx = (1 << 3), | ||||
|     VcpEvtRx = (1 << 4), | ||||
|     VcpEvtStreamTx = (1 << 5), | ||||
|     VcpEvtTx = (1 << 6), | ||||
| } WorkerEvtFlags; | ||||
| 
 | ||||
| #define VCP_THREAD_FLAG_ALL                                                                  \ | ||||
|     (VcpEvtEnable | VcpEvtDisable | VcpEvtConnect | VcpEvtDisconnect | VcpEvtRx | VcpEvtTx | \ | ||||
|      VcpEvtStreamRx | VcpEvtStreamTx) | ||||
| #define VCP_THREAD_FLAG_ALL                                                                 \ | ||||
|     (VcpEvtStop | VcpEvtConnect | VcpEvtDisconnect | VcpEvtRx | VcpEvtTx | VcpEvtStreamRx | \ | ||||
|      VcpEvtStreamTx) | ||||
| 
 | ||||
| typedef struct { | ||||
|     FuriThread* thread; | ||||
| @ -33,9 +33,12 @@ typedef struct { | ||||
|     StreamBufferHandle_t rx_stream; | ||||
| 
 | ||||
|     volatile bool connected; | ||||
|     volatile bool running; | ||||
| 
 | ||||
|     FuriHalUsbInterface* usb_if_prev; | ||||
| 
 | ||||
|     uint8_t data_buffer[USB_CDC_PKT_LEN]; | ||||
| } FuriHalVcp; | ||||
| } CliVcp; | ||||
| 
 | ||||
| static int32_t vcp_worker(void* context); | ||||
| static void vcp_on_cdc_tx_complete(void* context); | ||||
| @ -51,25 +54,23 @@ static CdcCallbacks cdc_cb = { | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| static FuriHalVcp* vcp = NULL; | ||||
| static CliVcp* vcp = NULL; | ||||
| 
 | ||||
| static const uint8_t ascii_soh = 0x01; | ||||
| static const uint8_t ascii_eot = 0x04; | ||||
| 
 | ||||
| void furi_hal_vcp_init() { | ||||
|     vcp = malloc(sizeof(FuriHalVcp)); | ||||
| static void cli_vcp_init() { | ||||
|     if(vcp == NULL) { | ||||
|         vcp = malloc(sizeof(CliVcp)); | ||||
|         vcp->tx_stream = xStreamBufferCreate(VCP_TX_BUF_SIZE, 1); | ||||
|         vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1); | ||||
|     } | ||||
|     furi_assert(vcp->thread == NULL); | ||||
| 
 | ||||
|     vcp->connected = false; | ||||
| 
 | ||||
|     vcp->tx_stream = xStreamBufferCreate(VCP_TX_BUF_SIZE, 1); | ||||
|     vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1); | ||||
| 
 | ||||
|     if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { | ||||
|         FURI_LOG_W(TAG, "Skipped worker init: device in special startup mode="); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     vcp->thread = furi_thread_alloc(); | ||||
|     furi_thread_set_name(vcp->thread, "VcpDriver"); | ||||
|     furi_thread_set_name(vcp->thread, "CliVcpWorker"); | ||||
|     furi_thread_set_stack_size(vcp->thread, 1024); | ||||
|     furi_thread_set_callback(vcp->thread, vcp_worker); | ||||
|     furi_thread_start(vcp->thread); | ||||
| @ -77,48 +78,35 @@ void furi_hal_vcp_init() { | ||||
|     FURI_LOG_I(TAG, "Init OK"); | ||||
| } | ||||
| 
 | ||||
| static void cli_vcp_deinit() { | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStop); | ||||
|     furi_thread_join(vcp->thread); | ||||
|     furi_thread_free(vcp->thread); | ||||
|     vcp->thread = NULL; | ||||
| } | ||||
| 
 | ||||
| static int32_t vcp_worker(void* context) { | ||||
|     bool enabled = true; | ||||
|     bool tx_idle = false; | ||||
|     bool tx_idle = true; | ||||
|     size_t missed_rx = 0; | ||||
|     uint8_t last_tx_pkt_len = 0; | ||||
| 
 | ||||
|     furi_hal_usb_set_config(&usb_cdc_single, NULL); | ||||
|     // Switch USB to VCP mode (if it is not set yet)
 | ||||
|     vcp->usb_if_prev = furi_hal_usb_get_config(); | ||||
|     if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) { | ||||
|         furi_hal_usb_set_config(&usb_cdc_single, NULL); | ||||
|     } | ||||
|     furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb, NULL); | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "Start"); | ||||
|     vcp->running = true; | ||||
| 
 | ||||
|     while(1) { | ||||
|         uint32_t flags = osThreadFlagsWait(VCP_THREAD_FLAG_ALL, osFlagsWaitAny, osWaitForever); | ||||
|         furi_assert((flags & osFlagsError) == 0); | ||||
| 
 | ||||
|         // VCP enabled
 | ||||
|         if((flags & VcpEvtEnable) && !enabled) { | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
|             FURI_LOG_D(TAG, "Enable"); | ||||
| #endif | ||||
|             flags |= VcpEvtTx; | ||||
|             furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb, NULL); | ||||
|             enabled = true; | ||||
|             furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); // flush Rx buffer
 | ||||
|             if(furi_hal_cdc_get_ctrl_line_state(VCP_IF_NUM) & (1 << 0)) { | ||||
|                 vcp->connected = true; | ||||
|                 xStreamBufferSend(vcp->rx_stream, &ascii_soh, 1, osWaitForever); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // VCP disabled
 | ||||
|         if((flags & VcpEvtDisable) && enabled) { | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
|             FURI_LOG_D(TAG, "Disable"); | ||||
| #endif | ||||
|             enabled = false; | ||||
|             vcp->connected = false; | ||||
|             xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); | ||||
|             xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, osWaitForever); | ||||
|         } | ||||
| 
 | ||||
|         // VCP session opened
 | ||||
|         if((flags & VcpEvtConnect) && enabled) { | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
|         if(flags & VcpEvtConnect) { | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|             FURI_LOG_D(TAG, "Connect"); | ||||
| #endif | ||||
|             if(vcp->connected == false) { | ||||
| @ -128,8 +116,8 @@ static int32_t vcp_worker(void* context) { | ||||
|         } | ||||
| 
 | ||||
|         // VCP session closed
 | ||||
|         if((flags & VcpEvtDisconnect) && enabled) { | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
|         if(flags & VcpEvtDisconnect) { | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|             FURI_LOG_D(TAG, "Disconnect"); | ||||
| #endif | ||||
|             if(vcp->connected == true) { | ||||
| @ -140,8 +128,8 @@ static int32_t vcp_worker(void* context) { | ||||
|         } | ||||
| 
 | ||||
|         // Rx buffer was read, maybe there is enough space for new data?
 | ||||
|         if((flags & VcpEvtStreamRx) && enabled && missed_rx > 0) { | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
|         if((flags & VcpEvtStreamRx) && (missed_rx > 0)) { | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|             FURI_LOG_D(TAG, "StreamRx"); | ||||
| #endif | ||||
|             if(xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) { | ||||
| @ -151,10 +139,10 @@ static int32_t vcp_worker(void* context) { | ||||
|         } | ||||
| 
 | ||||
|         // New data received
 | ||||
|         if((flags & VcpEvtRx)) { | ||||
|         if(flags & VcpEvtRx) { | ||||
|             if(xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) { | ||||
|                 int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|                 FURI_LOG_D(TAG, "Rx %d", len); | ||||
| #endif | ||||
|                 if(len > 0) { | ||||
| @ -163,7 +151,7 @@ static int32_t vcp_worker(void* context) { | ||||
|                         len); | ||||
|                 } | ||||
|             } else { | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|                 FURI_LOG_D(TAG, "Rx missed"); | ||||
| #endif | ||||
|                 missed_rx++; | ||||
| @ -171,8 +159,8 @@ static int32_t vcp_worker(void* context) { | ||||
|         } | ||||
| 
 | ||||
|         // New data in Tx buffer
 | ||||
|         if((flags & VcpEvtStreamTx) && enabled) { | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
|         if(flags & VcpEvtStreamTx) { | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|             FURI_LOG_D(TAG, "StreamTx"); | ||||
| #endif | ||||
|             if(tx_idle) { | ||||
| @ -181,10 +169,10 @@ static int32_t vcp_worker(void* context) { | ||||
|         } | ||||
| 
 | ||||
|         // CDC write transfer done
 | ||||
|         if((flags & VcpEvtTx) && enabled) { | ||||
|         if(flags & VcpEvtTx) { | ||||
|             size_t len = | ||||
|                 xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|             FURI_LOG_D(TAG, "Tx %d", len); | ||||
| #endif | ||||
|             if(len > 0) { // Some data left in Tx buffer. Sending it now
 | ||||
| @ -202,23 +190,33 @@ static int32_t vcp_worker(void* context) { | ||||
|                 last_tx_pkt_len = 0; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(flags & VcpEvtStop) { | ||||
|             vcp->connected = false; | ||||
|             vcp->running = false; | ||||
|             furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL); | ||||
|             // Restore previous USB mode (if it was set during init)
 | ||||
|             if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) { | ||||
|                 furi_hal_usb_set_config(vcp->usb_if_prev, NULL); | ||||
|             } | ||||
|             xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); | ||||
|             xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, osWaitForever); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     FURI_LOG_D(TAG, "End"); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| void furi_hal_vcp_enable() { | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtEnable); | ||||
| } | ||||
| 
 | ||||
| void furi_hal_vcp_disable() { | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisable); | ||||
| } | ||||
| 
 | ||||
| size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeout) { | ||||
| static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { | ||||
|     furi_assert(vcp); | ||||
|     furi_assert(buffer); | ||||
| 
 | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
|     if(vcp->running == false) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|     FURI_LOG_D(TAG, "rx %u start", size); | ||||
| #endif | ||||
| 
 | ||||
| @ -229,7 +227,7 @@ size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeo | ||||
|         if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE; | ||||
| 
 | ||||
|         size_t len = xStreamBufferReceive(vcp->rx_stream, buffer, batch_size, timeout); | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|         FURI_LOG_D(TAG, "rx %u ", batch_size); | ||||
| #endif | ||||
|         if(len == 0) break; | ||||
| @ -239,22 +237,21 @@ size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeo | ||||
|         rx_cnt += len; | ||||
|     } | ||||
| 
 | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|     FURI_LOG_D(TAG, "rx %u end", size); | ||||
| #endif | ||||
|     return rx_cnt; | ||||
| } | ||||
| 
 | ||||
| size_t furi_hal_vcp_rx(uint8_t* buffer, size_t size) { | ||||
|     furi_assert(vcp); | ||||
|     return furi_hal_vcp_rx_with_timeout(buffer, size, osWaitForever); | ||||
| } | ||||
| 
 | ||||
| void furi_hal_vcp_tx(const uint8_t* buffer, size_t size) { | ||||
| static void cli_vcp_tx(const uint8_t* buffer, size_t size) { | ||||
|     furi_assert(vcp); | ||||
|     furi_assert(buffer); | ||||
| 
 | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
|     if(vcp->running == false) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|     FURI_LOG_D(TAG, "tx %u start", size); | ||||
| #endif | ||||
| 
 | ||||
| @ -264,7 +261,7 @@ void furi_hal_vcp_tx(const uint8_t* buffer, size_t size) { | ||||
| 
 | ||||
|         xStreamBufferSend(vcp->tx_stream, buffer, batch_size, osWaitForever); | ||||
|         osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStreamTx); | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|         FURI_LOG_D(TAG, "tx %u", batch_size); | ||||
| #endif | ||||
| 
 | ||||
| @ -272,11 +269,15 @@ void furi_hal_vcp_tx(const uint8_t* buffer, size_t size) { | ||||
|         buffer += batch_size; | ||||
|     } | ||||
| 
 | ||||
| #ifdef FURI_HAL_USB_VCP_DEBUG | ||||
| #ifdef CLI_VCP_DEBUG | ||||
|     FURI_LOG_D(TAG, "tx %u end", size); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| static void cli_vcp_tx_stdout(void* _cookie, const char* data, size_t size) { | ||||
|     cli_vcp_tx((const uint8_t*)data, size); | ||||
| } | ||||
| 
 | ||||
| static void vcp_state_callback(void* context, uint8_t state) { | ||||
|     if(state == 0) { | ||||
|         osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisconnect); | ||||
| @ -303,7 +304,16 @@ static void vcp_on_cdc_tx_complete(void* context) { | ||||
|     osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtTx); | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_vcp_is_connected(void) { | ||||
| static bool cli_vcp_is_connected(void) { | ||||
|     furi_assert(vcp); | ||||
|     return vcp->connected; | ||||
| } | ||||
| 
 | ||||
| CliSession cli_vcp = { | ||||
|     cli_vcp_init, | ||||
|     cli_vcp_deinit, | ||||
|     cli_vcp_rx, | ||||
|     cli_vcp_tx, | ||||
|     cli_vcp_tx_stdout, | ||||
|     cli_vcp_is_connected, | ||||
| }; | ||||
							
								
								
									
										18
									
								
								applications/cli/cli_vcp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								applications/cli/cli_vcp.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| /**
 | ||||
|  * @file cli_vcp.h | ||||
|  * VCP HAL API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "cli_i.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| extern CliSession cli_vcp; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -45,7 +45,7 @@ typedef enum { | ||||
| #define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx) | ||||
| 
 | ||||
| const NotificationSequence sequence_notification = { | ||||
|     &message_display_on, | ||||
|     &message_display_backlight_on, | ||||
|     &message_green_255, | ||||
|     &message_delay_10, | ||||
|     NULL, | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
| #include "desktop/views/desktop_view_pin_input.h" | ||||
| #include "desktop/views/desktop_view_pin_timeout.h" | ||||
| #include "desktop_i.h" | ||||
| #include "desktop_helpers.h" | ||||
| #include "helpers/pin_lock.h" | ||||
| 
 | ||||
| static void desktop_auto_lock_arm(Desktop*); | ||||
| static void desktop_auto_lock_inhibit(Desktop*); | ||||
| @ -117,17 +117,18 @@ static void desktop_auto_lock_inhibit(Desktop* desktop) { | ||||
| } | ||||
| 
 | ||||
| void desktop_lock(Desktop* desktop) { | ||||
|     furi_hal_rtc_set_pin_fails(0); | ||||
|     desktop_auto_lock_inhibit(desktop); | ||||
|     scene_manager_set_scene_state( | ||||
|         desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER); | ||||
|     scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); | ||||
|     notification_message(desktop->notification, &sequence_display_off_delay_1000); | ||||
|     notification_message(desktop->notification, &sequence_display_backlight_off_delay_1000); | ||||
| } | ||||
| 
 | ||||
| void desktop_unlock(Desktop* desktop) { | ||||
|     furi_hal_rtc_set_pin_fails(0); | ||||
|     desktop_helpers_unlock_system(desktop); | ||||
|     view_port_enabled_set(desktop->lock_viewport, false); | ||||
|     Gui* gui = furi_record_open("gui"); | ||||
|     gui_set_lockdown(gui, false); | ||||
|     furi_record_close("gui"); | ||||
|     desktop_view_locked_unlock(desktop->locked_view); | ||||
|     scene_manager_search_and_switch_to_previous_scene(desktop->scene_manager, DesktopSceneMain); | ||||
|     desktop_auto_lock_arm(desktop); | ||||
| @ -301,18 +302,15 @@ int32_t desktop_srv(void* p) { | ||||
| 
 | ||||
|     bool loaded = LOAD_DESKTOP_SETTINGS(&desktop->settings); | ||||
|     if(!loaded) { | ||||
|         furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); | ||||
|         memset(&desktop->settings, 0, sizeof(desktop->settings)); | ||||
|         SAVE_DESKTOP_SETTINGS(&desktop->settings); | ||||
|     } | ||||
| 
 | ||||
|     scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||
| 
 | ||||
|     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock) && !desktop->settings.pin_code.length) { | ||||
|         furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); | ||||
|     } | ||||
|     desktop_pin_lock_init(&desktop->settings); | ||||
| 
 | ||||
|     if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { | ||||
|     if(!desktop_pin_lock_is_locked()) { | ||||
|         if(!loader_is_locked(desktop->loader)) { | ||||
|             desktop_auto_lock_arm(desktop); | ||||
|         } | ||||
|  | ||||
| @ -1,82 +0,0 @@ | ||||
| #include <notification/notification.h> | ||||
| #include <notification/notification_messages.h> | ||||
| #include <stddef.h> | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <gui/gui.h> | ||||
| 
 | ||||
| #include "desktop_helpers.h" | ||||
| #include "desktop_i.h" | ||||
| 
 | ||||
| static const NotificationSequence sequence_pin_fail = { | ||||
|     &message_display_on, | ||||
| 
 | ||||
|     &message_red_255, | ||||
|     &message_vibro_on, | ||||
|     &message_delay_100, | ||||
|     &message_vibro_off, | ||||
|     &message_red_0, | ||||
| 
 | ||||
|     &message_delay_250, | ||||
| 
 | ||||
|     &message_red_255, | ||||
|     &message_vibro_on, | ||||
|     &message_delay_100, | ||||
|     &message_vibro_off, | ||||
|     &message_red_0, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| static const uint8_t desktop_helpers_fails_timeout[] = { | ||||
|     0, | ||||
|     0, | ||||
|     0, | ||||
|     0, | ||||
|     30, | ||||
|     60, | ||||
|     90, | ||||
|     120, | ||||
|     150, | ||||
|     180, | ||||
|     /* +60 for every next fail */ | ||||
| }; | ||||
| 
 | ||||
| void desktop_helpers_emit_error_notification() { | ||||
|     NotificationApp* notification = furi_record_open("notification"); | ||||
|     notification_message(notification, &sequence_pin_fail); | ||||
|     furi_record_close("notification"); | ||||
| } | ||||
| 
 | ||||
| void desktop_helpers_lock_system(Desktop* desktop, bool hard_lock) { | ||||
|     view_port_enabled_set(desktop->lock_viewport, true); | ||||
|     if(hard_lock) { | ||||
|         furi_hal_rtc_set_flag(FuriHalRtcFlagLock); | ||||
|         furi_hal_usb_disable(); | ||||
|     } | ||||
| 
 | ||||
|     Gui* gui = furi_record_open("gui"); | ||||
|     gui_set_lockdown(gui, true); | ||||
|     furi_record_close("gui"); | ||||
| } | ||||
| 
 | ||||
| void desktop_helpers_unlock_system(Desktop* desktop) { | ||||
|     furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); | ||||
|     furi_hal_usb_enable(); | ||||
|     view_port_enabled_set(desktop->lock_viewport, false); | ||||
| 
 | ||||
|     Gui* gui = furi_record_open("gui"); | ||||
|     gui_set_lockdown(gui, false); | ||||
|     furi_record_close("gui"); | ||||
| } | ||||
| 
 | ||||
| uint32_t desktop_helpers_get_pin_fail_timeout(uint32_t pin_fails) { | ||||
|     uint32_t pin_timeout = 0; | ||||
|     uint32_t max_index = COUNT_OF(desktop_helpers_fails_timeout) - 1; | ||||
|     if(pin_fails <= max_index) { | ||||
|         pin_timeout = desktop_helpers_fails_timeout[pin_fails]; | ||||
|     } else { | ||||
|         pin_timeout = desktop_helpers_fails_timeout[max_index] + (pin_fails - max_index) * 60; | ||||
|     } | ||||
| 
 | ||||
|     return pin_timeout; | ||||
| } | ||||
| @ -1,9 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| #include "desktop.h" | ||||
| 
 | ||||
| void desktop_helpers_emit_error_notification(); | ||||
| void desktop_helpers_lock_system(Desktop* desktop, bool hard_lock); | ||||
| void desktop_helpers_unlock_system(Desktop* desktop); | ||||
| uint32_t desktop_helpers_get_pin_fail_timeout(uint32_t pin_fails); | ||||
| @ -5,7 +5,7 @@ | ||||
| #include <stdbool.h> | ||||
| #include <toolbox/saved_struct.h> | ||||
| 
 | ||||
| #define DESKTOP_SETTINGS_VER (2) | ||||
| #define DESKTOP_SETTINGS_VER (3) | ||||
| #define DESKTOP_SETTINGS_PATH "/int/desktop.settings" | ||||
| #define DESKTOP_SETTINGS_MAGIC (0x17) | ||||
| #define PIN_MAX_LENGTH 12 | ||||
| @ -39,17 +39,6 @@ typedef struct { | ||||
| typedef struct { | ||||
|     uint16_t favorite; | ||||
|     PinCode pin_code; | ||||
|     uint8_t is_locked; | ||||
|     uint32_t auto_lock_delay_ms; | ||||
| } DesktopSettings; | ||||
| 
 | ||||
| static inline bool pins_are_equal(const PinCode* pin_code1, const PinCode* pin_code2) { | ||||
|     furi_assert(pin_code1); | ||||
|     furi_assert(pin_code2); | ||||
|     bool result = false; | ||||
| 
 | ||||
|     if(pin_code1->length == pin_code2->length) { | ||||
|         result = !memcmp(pin_code1->data, pin_code2->data, pin_code1->length); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| #include <stdint.h> | ||||
| #include <furi/check.h> | ||||
| #include <gui/scene_manager.h> | ||||
| 
 | ||||
| #include "../../helpers/pin_lock.h" | ||||
| #include "../desktop_settings_app.h" | ||||
| #include "desktop/desktop_settings/desktop_settings.h" | ||||
| #include "desktop/views/desktop_view_pin_input.h" | ||||
| @ -18,7 +18,7 @@ static void pin_auth_done_callback(const PinCode* pin_code, void* context) { | ||||
|     DesktopSettingsApp* app = context; | ||||
| 
 | ||||
|     app->pincode_buffer = *pin_code; | ||||
|     if(pins_are_equal(&app->settings.pin_code, pin_code)) { | ||||
|     if(desktop_pins_are_equal(&app->settings.pin_code, pin_code)) { | ||||
|         view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL); | ||||
|     } else { | ||||
|         view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT); | ||||
|  | ||||
| @ -6,7 +6,7 @@ | ||||
| #include "desktop/views/desktop_view_pin_input.h" | ||||
| #include "desktop_settings_scene.h" | ||||
| #include "desktop_settings_scene_i.h" | ||||
| #include "../../desktop_helpers.h" | ||||
| #include "../../helpers/pin_lock.h" | ||||
| #include "../desktop_settings_app.h" | ||||
| 
 | ||||
| #define SCENE_EVENT_EXIT (0U) | ||||
| @ -25,7 +25,7 @@ static void pin_error_done_callback(const PinCode* pin_code, void* context) { | ||||
| 
 | ||||
| void desktop_settings_scene_pin_error_on_enter(void* context) { | ||||
|     DesktopSettingsApp* app = context; | ||||
|     desktop_helpers_emit_error_notification(); | ||||
|     desktop_pin_lock_error_notify(); | ||||
| 
 | ||||
|     desktop_view_pin_input_set_context(app->pin_input_view, app); | ||||
|     desktop_view_pin_input_set_back_callback(app->pin_input_view, pin_error_back_callback); | ||||
|  | ||||
| @ -7,6 +7,7 @@ | ||||
| #include "desktop/views/desktop_view_pin_input.h" | ||||
| #include "desktop_settings_scene.h" | ||||
| #include "desktop_settings_scene_i.h" | ||||
| #include "../../helpers/pin_lock.h" | ||||
| 
 | ||||
| #define SCENE_EVENT_EXIT (0U) | ||||
| #define SCENE_EVENT_1ST_PIN_ENTERED (1U) | ||||
| @ -24,7 +25,7 @@ static void pin_setup_done_callback(const PinCode* pin_code, void* context) { | ||||
|         view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_1ST_PIN_ENTERED); | ||||
|     } else { | ||||
|         app->pincode_buffer_filled = false; | ||||
|         if(pins_are_equal(&app->pincode_buffer, pin_code)) { | ||||
|         if(desktop_pins_are_equal(&app->pincode_buffer, pin_code)) { | ||||
|             view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL); | ||||
|         } else { | ||||
|             view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT); | ||||
|  | ||||
							
								
								
									
										138
									
								
								applications/desktop/helpers/pin_lock.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								applications/desktop/helpers/pin_lock.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,138 @@ | ||||
| 
 | ||||
| #include <notification/notification.h> | ||||
| #include <notification/notification_messages.h> | ||||
| #include <stddef.h> | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <gui/gui.h> | ||||
| 
 | ||||
| #include "../helpers/pin_lock.h" | ||||
| #include "../desktop_i.h" | ||||
| #include <cli/cli_vcp.h> | ||||
| 
 | ||||
| static const NotificationSequence sequence_pin_fail = { | ||||
|     &message_display_backlight_on, | ||||
| 
 | ||||
|     &message_red_255, | ||||
|     &message_vibro_on, | ||||
|     &message_delay_100, | ||||
|     &message_vibro_off, | ||||
|     &message_red_0, | ||||
| 
 | ||||
|     &message_delay_250, | ||||
| 
 | ||||
|     &message_red_255, | ||||
|     &message_vibro_on, | ||||
|     &message_delay_100, | ||||
|     &message_vibro_off, | ||||
|     &message_red_0, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| static const uint8_t desktop_helpers_fails_timeout[] = { | ||||
|     0, | ||||
|     0, | ||||
|     0, | ||||
|     0, | ||||
|     30, | ||||
|     60, | ||||
|     90, | ||||
|     120, | ||||
|     150, | ||||
|     180, | ||||
|     /* +60 for every next fail */ | ||||
| }; | ||||
| 
 | ||||
| void desktop_pin_lock_error_notify() { | ||||
|     NotificationApp* notification = furi_record_open("notification"); | ||||
|     notification_message(notification, &sequence_pin_fail); | ||||
|     furi_record_close("notification"); | ||||
| } | ||||
| 
 | ||||
| uint32_t desktop_pin_lock_get_fail_timeout() { | ||||
|     uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); | ||||
|     uint32_t pin_timeout = 0; | ||||
|     uint32_t max_index = COUNT_OF(desktop_helpers_fails_timeout) - 1; | ||||
|     if(pin_fails <= max_index) { | ||||
|         pin_timeout = desktop_helpers_fails_timeout[pin_fails]; | ||||
|     } else { | ||||
|         pin_timeout = desktop_helpers_fails_timeout[max_index] + (pin_fails - max_index) * 60; | ||||
|     } | ||||
| 
 | ||||
|     return pin_timeout; | ||||
| } | ||||
| 
 | ||||
| void desktop_pin_lock(DesktopSettings* settings) { | ||||
|     furi_assert(settings); | ||||
| 
 | ||||
|     furi_hal_rtc_set_pin_fails(0); | ||||
|     furi_hal_rtc_set_flag(FuriHalRtcFlagLock); | ||||
|     Cli* cli = furi_record_open("cli"); | ||||
|     cli_session_close(cli); | ||||
|     furi_record_close("cli"); | ||||
|     settings->is_locked = 1; | ||||
|     SAVE_DESKTOP_SETTINGS(settings); | ||||
| } | ||||
| 
 | ||||
| void desktop_pin_unlock(DesktopSettings* settings) { | ||||
|     furi_assert(settings); | ||||
| 
 | ||||
|     furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); | ||||
|     Cli* cli = furi_record_open("cli"); | ||||
|     cli_session_open(cli, &cli_vcp); | ||||
|     furi_record_close("cli"); | ||||
|     settings->is_locked = 0; | ||||
|     SAVE_DESKTOP_SETTINGS(settings); | ||||
| } | ||||
| 
 | ||||
| void desktop_pin_lock_init(DesktopSettings* settings) { | ||||
|     furi_assert(settings); | ||||
| 
 | ||||
|     if(settings->pin_code.length > 0) { | ||||
|         if(settings->is_locked == 1) { | ||||
|             furi_hal_rtc_set_flag(FuriHalRtcFlagLock); | ||||
|         } else { | ||||
|             if(desktop_pin_lock_is_locked()) { | ||||
|                 settings->is_locked = 1; | ||||
|                 SAVE_DESKTOP_SETTINGS(settings); | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         furi_hal_rtc_set_pin_fails(0); | ||||
|         furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); | ||||
|         furi_hal_usb_enable(); | ||||
|     } | ||||
| 
 | ||||
|     if(desktop_pin_lock_is_locked()) { | ||||
|         furi_hal_usb_disable(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool desktop_pin_lock_verify(const PinCode* pin_set, const PinCode* pin_entered) { | ||||
|     bool result = false; | ||||
|     if(desktop_pins_are_equal(pin_set, pin_entered)) { | ||||
|         furi_hal_rtc_set_pin_fails(0); | ||||
|         result = true; | ||||
|     } else { | ||||
|         uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); | ||||
|         furi_hal_rtc_set_pin_fails(pin_fails + 1); | ||||
|         result = false; | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool desktop_pin_lock_is_locked() { | ||||
|     return furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock); | ||||
| } | ||||
| 
 | ||||
| bool desktop_pins_are_equal(const PinCode* pin_code1, const PinCode* pin_code2) { | ||||
|     furi_assert(pin_code1); | ||||
|     furi_assert(pin_code2); | ||||
|     bool result = false; | ||||
| 
 | ||||
|     if(pin_code1->length == pin_code2->length) { | ||||
|         result = !memcmp(pin_code1->data, pin_code2->data, pin_code1->length); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
							
								
								
									
										21
									
								
								applications/desktop/helpers/pin_lock.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								applications/desktop/helpers/pin_lock.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| #pragma once | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| #include "../desktop.h" | ||||
| #include "../desktop_settings/desktop_settings.h" | ||||
| 
 | ||||
| void desktop_pin_lock_error_notify(); | ||||
| 
 | ||||
| uint32_t desktop_pin_lock_get_fail_timeout(); | ||||
| 
 | ||||
| void desktop_pin_lock(DesktopSettings* settings); | ||||
| 
 | ||||
| void desktop_pin_unlock(DesktopSettings* settings); | ||||
| 
 | ||||
| bool desktop_pin_lock_is_locked(); | ||||
| 
 | ||||
| void desktop_pin_lock_init(DesktopSettings* settings); | ||||
| 
 | ||||
| bool desktop_pin_lock_verify(const PinCode* pin_set, const PinCode* pin_entered); | ||||
| 
 | ||||
| bool desktop_pins_are_equal(const PinCode* pin_code1, const PinCode* pin_code2); | ||||
| @ -10,6 +10,7 @@ | ||||
| #include "../views/desktop_view_lock_menu.h" | ||||
| #include "desktop_scene_i.h" | ||||
| #include "desktop_scene.h" | ||||
| #include "../helpers/pin_lock.h" | ||||
| 
 | ||||
| #define TAG "DesktopSceneLock" | ||||
| 
 | ||||
| @ -53,7 +54,7 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { | ||||
|             break; | ||||
|         case DesktopLockMenuEventPinLock: | ||||
|             if(desktop->settings.pin_code.length > 0) { | ||||
|                 furi_hal_rtc_set_flag(FuriHalRtcFlagLock); | ||||
|                 desktop_pin_lock(&desktop->settings); | ||||
|                 desktop_lock(desktop); | ||||
|             } else { | ||||
|                 LoaderStatus status = | ||||
| @ -64,7 +65,6 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { | ||||
|                     FURI_LOG_E(TAG, "Unable to start desktop settings"); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case DesktopLockMenuEventExit: | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
| 
 | ||||
| #include "../desktop.h" | ||||
| #include "../desktop_i.h" | ||||
| #include "../desktop_helpers.h" | ||||
| #include "../helpers/pin_lock.h" | ||||
| #include "../animations/animation_manager.h" | ||||
| #include "../views/desktop_events.h" | ||||
| #include "../views/desktop_view_pin_input.h" | ||||
| @ -45,14 +45,17 @@ void desktop_scene_locked_on_enter(void* context) { | ||||
|     bool switch_to_timeout_scene = false; | ||||
|     uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLocked); | ||||
|     if(state == SCENE_LOCKED_FIRST_ENTER) { | ||||
|         bool pin_locked = furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock); | ||||
|         desktop_helpers_lock_system(desktop, pin_locked); | ||||
|         bool pin_locked = desktop_pin_lock_is_locked(); | ||||
|         view_port_enabled_set(desktop->lock_viewport, true); | ||||
|         Gui* gui = furi_record_open("gui"); | ||||
|         gui_set_lockdown(gui, true); | ||||
|         furi_record_close("gui"); | ||||
| 
 | ||||
|         if(pin_locked) { | ||||
|             LOAD_DESKTOP_SETTINGS(&desktop->settings); | ||||
|             desktop_view_locked_lock(desktop->locked_view, true); | ||||
|             uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); | ||||
|             uint32_t pin_timeout = desktop_helpers_get_pin_fail_timeout(pin_fails); | ||||
|             if(pin_timeout) { | ||||
|             uint32_t pin_timeout = desktop_pin_lock_get_fail_timeout(); | ||||
|             if(pin_timeout > 0) { | ||||
|                 scene_manager_set_scene_state( | ||||
|                     desktop->scene_manager, DesktopScenePinTimeout, pin_timeout); | ||||
|                 switch_to_timeout_scene = true; | ||||
| @ -86,7 +89,7 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { | ||||
|             break; | ||||
|         case DesktopLockedEventUpdate: | ||||
|             if(desktop_view_locked_is_locked_hint_visible(desktop->locked_view)) { | ||||
|                 notification_message(desktop->notification, &sequence_display_off); | ||||
|                 notification_message(desktop->notification, &sequence_display_backlight_off); | ||||
|             } | ||||
|             desktop_view_locked_update(desktop->locked_view); | ||||
|             consumed = true; | ||||
|  | ||||
| @ -12,7 +12,7 @@ | ||||
| #include "../animations/animation_manager.h" | ||||
| #include "../views/desktop_events.h" | ||||
| #include "../views/desktop_view_pin_input.h" | ||||
| #include "../desktop_helpers.h" | ||||
| #include "../helpers/pin_lock.h" | ||||
| #include "desktop_scene.h" | ||||
| #include "desktop_scene_i.h" | ||||
| 
 | ||||
| @ -54,7 +54,7 @@ static void desktop_scene_pin_input_back_callback(void* context) { | ||||
| 
 | ||||
| static void desktop_scene_pin_input_done_callback(const PinCode* pin_code, void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     if(pins_are_equal(&desktop->settings.pin_code, pin_code)) { | ||||
|     if(desktop_pin_lock_verify(&desktop->settings.pin_code, pin_code)) { | ||||
|         view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventUnlocked); | ||||
|     } else { | ||||
|         view_dispatcher_send_custom_event( | ||||
| @ -97,17 +97,14 @@ void desktop_scene_pin_input_on_enter(void* context) { | ||||
| bool desktop_scene_pin_input_on_event(void* context, SceneManagerEvent event) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     bool consumed = false; | ||||
|     uint32_t pin_fails = 0; | ||||
|     uint32_t pin_timeout = 0; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DesktopPinInputEventUnlockFailed: | ||||
|             pin_fails = furi_hal_rtc_get_pin_fails(); | ||||
|             pin_fails++; | ||||
|             furi_hal_rtc_set_pin_fails(pin_fails); | ||||
|             uint32_t pin_timeout = desktop_helpers_get_pin_fail_timeout(pin_fails); | ||||
|             pin_timeout = desktop_pin_lock_get_fail_timeout(); | ||||
|             if(pin_timeout > 0) { | ||||
|                 desktop_helpers_emit_error_notification(); | ||||
|                 desktop_pin_lock_error_notify(); | ||||
|                 scene_manager_set_scene_state( | ||||
|                     desktop->scene_manager, DesktopScenePinTimeout, pin_timeout); | ||||
|                 scene_manager_next_scene(desktop->scene_manager, DesktopScenePinTimeout); | ||||
| @ -129,13 +126,14 @@ bool desktop_scene_pin_input_on_event(void* context, SceneManagerEvent event) { | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case DesktopPinInputEventUnlocked: | ||||
|             desktop_pin_unlock(&desktop->settings); | ||||
|             desktop_unlock(desktop); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case DesktopPinInputEventBack: | ||||
|             scene_manager_search_and_switch_to_previous_scene( | ||||
|                 desktop->scene_manager, DesktopSceneLocked); | ||||
|             notification_message(desktop->notification, &sequence_display_off); | ||||
|             notification_message(desktop->notification, &sequence_display_backlight_off); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
| @ -148,7 +148,7 @@ void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) { | ||||
|         dolphin_deed_get_app_limit(app) - dolphin_state->data.icounter_daily_limit[app]; | ||||
|     uint8_t deed_weight = CLAMP(dolphin_deed_get_weight(deed), weight_limit, 0); | ||||
| 
 | ||||
|     uint8_t xp_to_levelup = dolphin_state_xp_to_levelup(dolphin_state->data.icounter); | ||||
|     uint32_t xp_to_levelup = dolphin_state_xp_to_levelup(dolphin_state->data.icounter); | ||||
|     if(xp_to_levelup) { | ||||
|         deed_weight = MIN(xp_to_levelup, deed_weight); | ||||
|         dolphin_state->data.icounter += deed_weight; | ||||
|  | ||||
| @ -33,7 +33,7 @@ void gpio_scene_usb_uart_on_enter(void* context) { | ||||
|     gpio_usb_uart_set_callback(app->gpio_usb_uart, gpio_scene_usb_uart_callback, app); | ||||
|     scene_manager_set_scene_state(app->scene_manager, GpioSceneUsbUart, 0); | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUart); | ||||
|     notification_message(app->notifications, &sequence_display_lock); | ||||
|     notification_message(app->notifications, &sequence_display_backlight_enforce_on); | ||||
| } | ||||
| 
 | ||||
| bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { | ||||
| @ -63,5 +63,5 @@ void gpio_scene_usb_uart_on_exit(void* context) { | ||||
|         usb_uart_disable(app->usb_uart_bridge); | ||||
|         free(scene_usb_uart); | ||||
|     } | ||||
|     notification_message(app->notifications, &sequence_display_unlock); | ||||
|     notification_message(app->notifications, &sequence_display_backlight_enforce_auto); | ||||
| } | ||||
|  | ||||
| @ -3,6 +3,8 @@ | ||||
| #include <stream_buffer.h> | ||||
| #include <furi_hal_usb_cdc_i.h> | ||||
| #include "usb_cdc.h" | ||||
| #include "cli/cli_vcp.h" | ||||
| #include "cli/cli.h" | ||||
| 
 | ||||
| #define USB_CDC_PKT_LEN CDC_DATA_SZ | ||||
| #define USB_UART_RX_BUF_SIZE (USB_CDC_PKT_LEN * 5) | ||||
| @ -16,17 +18,16 @@ static const GpioPin* flow_pins[][2] = { | ||||
| }; | ||||
| 
 | ||||
| typedef enum { | ||||
|     WorkerEvtReserved = (1 << 0), // Reserved for StreamBuffer internal event
 | ||||
|     WorkerEvtStop = (1 << 1), | ||||
|     WorkerEvtRxDone = (1 << 2), | ||||
|     WorkerEvtStop = (1 << 0), | ||||
|     WorkerEvtRxDone = (1 << 1), | ||||
| 
 | ||||
|     WorkerEvtTxStop = (1 << 3), | ||||
|     WorkerEvtCdcRx = (1 << 4), | ||||
|     WorkerEvtTxStop = (1 << 2), | ||||
|     WorkerEvtCdcRx = (1 << 3), | ||||
| 
 | ||||
|     WorkerEvtCfgChange = (1 << 5), | ||||
|     WorkerEvtCfgChange = (1 << 4), | ||||
| 
 | ||||
|     WorkerEvtLineCfgSet = (1 << 6), | ||||
|     WorkerEvtCtrlLineSet = (1 << 7), | ||||
|     WorkerEvtLineCfgSet = (1 << 5), | ||||
|     WorkerEvtCtrlLineSet = (1 << 6), | ||||
| 
 | ||||
| } WorkerEvtFlags; | ||||
| 
 | ||||
| @ -84,18 +85,29 @@ static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { | ||||
| 
 | ||||
| static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) { | ||||
|     furi_hal_usb_unlock(); | ||||
|     FURI_LOG_I("", "Init %d", vcp_ch); | ||||
|     if(vcp_ch == 0) { | ||||
|         Cli* cli = furi_record_open("cli"); | ||||
|         cli_session_close(cli); | ||||
|         furi_record_close("cli"); | ||||
|         furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); | ||||
|         furi_hal_vcp_disable(); | ||||
|     } else { | ||||
|         furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true); | ||||
|         Cli* cli = furi_record_open("cli"); | ||||
|         cli_session_open(cli, &cli_vcp); | ||||
|         furi_record_close("cli"); | ||||
|     } | ||||
|     furi_hal_cdc_set_callbacks(vcp_ch, (CdcCallbacks*)&cdc_cb, usb_uart); | ||||
| } | ||||
| 
 | ||||
| static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) { | ||||
|     furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL); | ||||
|     if(vcp_ch == 0) furi_hal_vcp_enable(); | ||||
|     FURI_LOG_I("", "Deinit %d", vcp_ch); | ||||
|     if(vcp_ch != 0) { | ||||
|         Cli* cli = furi_record_open("cli"); | ||||
|         cli_session_close(cli); | ||||
|         furi_record_close("cli"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void usb_uart_serial_init(UsbUartBridge* usb_uart, uint8_t uart_ch) { | ||||
| @ -155,7 +167,6 @@ static int32_t usb_uart_worker(void* context) { | ||||
|     furi_thread_set_context(usb_uart->tx_thread, usb_uart); | ||||
|     furi_thread_set_callback(usb_uart->tx_thread, usb_uart_tx_thread); | ||||
| 
 | ||||
|     FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); | ||||
|     usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_ch); | ||||
|     usb_uart_serial_init(usb_uart, usb_uart->cfg.uart_ch); | ||||
|     usb_uart_set_baudrate(usb_uart, usb_uart->cfg.baudrate); | ||||
| @ -247,11 +258,9 @@ static int32_t usb_uart_worker(void* context) { | ||||
|             usb_uart_update_ctrl_lines(usb_uart); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch); | ||||
|     usb_uart_serial_deinit(usb_uart, usb_uart->cfg.uart_ch); | ||||
|     furi_hal_usb_unlock(); | ||||
|     furi_hal_usb_set_config(usb_mode_prev, NULL); | ||||
| 
 | ||||
|     if(usb_uart->cfg.flow_pins != 0) { | ||||
|         furi_hal_gpio_init_simple(flow_pins[usb_uart->cfg.flow_pins - 1][0], GpioModeAnalog); | ||||
|         furi_hal_gpio_init_simple(flow_pins[usb_uart->cfg.flow_pins - 1][1], GpioModeAnalog); | ||||
| @ -265,6 +274,12 @@ static int32_t usb_uart_worker(void* context) { | ||||
|     osMutexDelete(usb_uart->usb_mutex); | ||||
|     osSemaphoreDelete(usb_uart->tx_sem); | ||||
| 
 | ||||
|     furi_hal_usb_unlock(); | ||||
|     furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); | ||||
|     Cli* cli = furi_record_open("cli"); | ||||
|     cli_session_open(cli, &cli_vcp); | ||||
|     furi_record_close("cli"); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -17,8 +17,6 @@ const CanvasFontParameters canvas_font_params[FontTotalNumber] = { | ||||
| Canvas* canvas_init() { | ||||
|     Canvas* canvas = malloc(sizeof(Canvas)); | ||||
| 
 | ||||
|     furi_hal_power_insomnia_enter(); | ||||
| 
 | ||||
|     // Setup u8g2
 | ||||
|     u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); | ||||
|     canvas->orientation = CanvasOrientationHorizontal; | ||||
| @ -31,8 +29,6 @@ Canvas* canvas_init() { | ||||
|     canvas_clear(canvas); | ||||
|     canvas_commit(canvas); | ||||
| 
 | ||||
|     furi_hal_power_insomnia_exit(); | ||||
| 
 | ||||
|     return canvas; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -41,7 +41,6 @@ iButtonApp::iButtonApp() | ||||
|     : notification{"notification"} | ||||
|     , storage{"storage"} | ||||
|     , dialogs{"dialogs"} { | ||||
|     furi_hal_power_insomnia_enter(); | ||||
|     key = ibutton_key_alloc(); | ||||
|     key_worker = ibutton_worker_alloc(); | ||||
|     ibutton_worker_start_thread(key_worker); | ||||
| @ -56,8 +55,6 @@ iButtonApp::~iButtonApp() { | ||||
|     ibutton_worker_stop_thread(key_worker); | ||||
|     ibutton_worker_free(key_worker); | ||||
|     ibutton_key_free(key); | ||||
| 
 | ||||
|     furi_hal_power_insomnia_exit(); | ||||
| } | ||||
| 
 | ||||
| iButtonAppViewManager* iButtonApp::get_view_manager() { | ||||
| @ -133,18 +130,18 @@ uint8_t iButtonApp::get_file_name_size() { | ||||
|     return file_name_size; | ||||
| } | ||||
| 
 | ||||
| void iButtonApp::notify_green_blink() { | ||||
|     notification_message(notification, &sequence_blink_green_10); | ||||
| void iButtonApp::notify_read() { | ||||
|     notification_message(notification, &sequence_blink_cyan_10); | ||||
| } | ||||
| 
 | ||||
| void iButtonApp::notify_emulate() { | ||||
|     notification_message(notification, &sequence_blink_magenta_10); | ||||
| } | ||||
| 
 | ||||
| void iButtonApp::notify_yellow_blink() { | ||||
|     notification_message(notification, &sequence_blink_yellow_10); | ||||
| } | ||||
| 
 | ||||
| void iButtonApp::notify_red_blink() { | ||||
|     notification_message(notification, &sequence_blink_red_10); | ||||
| } | ||||
| 
 | ||||
| void iButtonApp::notify_error() { | ||||
|     notification_message(notification, &sequence_error); | ||||
| } | ||||
| @ -342,4 +339,4 @@ void iButtonApp::make_app_folder() { | ||||
|     if(!storage_simply_mkdir(storage, app_folder)) { | ||||
|         dialog_message_show_storage_error(dialogs, "Cannot create\napp folder"); | ||||
|     } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -75,9 +75,9 @@ public: | ||||
|     iButtonWorker* get_key_worker(); | ||||
|     iButtonKey* get_key(); | ||||
| 
 | ||||
|     void notify_green_blink(); | ||||
|     void notify_read(); | ||||
|     void notify_yellow_blink(); | ||||
|     void notify_red_blink(); | ||||
|     void notify_emulate(); | ||||
| 
 | ||||
|     void notify_error(); | ||||
|     void notify_success(); | ||||
|  | ||||
| @ -83,7 +83,7 @@ bool iButtonSceneEmulate::on_event(iButtonApp* app, iButtonEvent* event) { | ||||
|         app->notify_yellow_blink(); | ||||
|         consumed = true; | ||||
|     } else if(event->type == iButtonEvent::Type::EventTypeTick) { | ||||
|         app->notify_red_blink(); | ||||
|         app->notify_emulate(); | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -54,7 +54,7 @@ bool iButtonSceneRead::on_event(iButtonApp* app, iButtonEvent* event) { | ||||
|         } | ||||
|     } else if(event->type == iButtonEvent::Type::EventTypeTick) { | ||||
|         consumed = true; | ||||
|         app->notify_red_blink(); | ||||
|         app->notify_read(); | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
|  | ||||
| @ -95,7 +95,7 @@ bool iButtonSceneWrite::on_event(iButtonApp* app, iButtonEvent* event) { | ||||
|         if(blink_yellow) { | ||||
|             app->notify_yellow_blink(); | ||||
|         } else { | ||||
|             app->notify_red_blink(); | ||||
|             app->notify_emulate(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -211,46 +211,12 @@ void InfraredApp::notify_success() { | ||||
|     notification_message(notification, &sequence_success); | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::notify_red_blink() { | ||||
|     notification_message(notification, &sequence_blink_red_10); | ||||
| void InfraredApp::notify_blink_read() { | ||||
|     notification_message(notification, &sequence_blink_cyan_10); | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::notify_click() { | ||||
|     static const NotificationSequence sequence = { | ||||
|         &message_click, | ||||
|         &message_delay_1, | ||||
|         &message_sound_off, | ||||
|         NULL, | ||||
|     }; | ||||
| 
 | ||||
|     notification_message_block(notification, &sequence); | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::notify_click_and_green_blink() { | ||||
|     static const NotificationSequence sequence = { | ||||
|         &message_click, | ||||
|         &message_delay_1, | ||||
|         &message_sound_off, | ||||
|         &message_green_255, | ||||
|         &message_delay_10, | ||||
|         &message_green_0, | ||||
|         &message_do_not_reset, | ||||
|         NULL, | ||||
|     }; | ||||
| 
 | ||||
|     notification_message_block(notification, &sequence); | ||||
| } | ||||
| 
 | ||||
| void InfraredApp::notify_blink_green() { | ||||
|     static const NotificationSequence sequence = { | ||||
|         &message_green_255, | ||||
|         &message_delay_10, | ||||
|         &message_green_0, | ||||
|         &message_do_not_reset, | ||||
|         NULL, | ||||
|     }; | ||||
| 
 | ||||
|     notification_message(notification, &sequence); | ||||
| void InfraredApp::notify_blink_send() { | ||||
|     notification_message(notification, &sequence_blink_magenta_10); | ||||
| } | ||||
| 
 | ||||
| DialogsApp* InfraredApp::get_dialogs() { | ||||
| @ -279,5 +245,5 @@ void InfraredApp::set_received_signal(const InfraredAppSignal& signal) { | ||||
| 
 | ||||
| void InfraredApp::signal_sent_callback(void* context) { | ||||
|     InfraredApp* app = static_cast<InfraredApp*>(context); | ||||
|     app->notify_blink_green(); | ||||
|     app->notify_blink_send(); | ||||
| } | ||||
|  | ||||
| @ -217,17 +217,13 @@ public: | ||||
|     /** Play success notification */ | ||||
|     void notify_success(); | ||||
|     /** Play red blink notification */ | ||||
|     void notify_red_blink(); | ||||
|     void notify_blink_read(); | ||||
|     /** Light green */ | ||||
|     void notify_green_on(); | ||||
|     /** Disable green light */ | ||||
|     void notify_green_off(); | ||||
|     /** Play click sound */ | ||||
|     void notify_click(); | ||||
|     /** Play click and green notification */ | ||||
|     void notify_click_and_green_blink(); | ||||
|     /** Blink green light */ | ||||
|     void notify_blink_green(); | ||||
|     /** Blink on send */ | ||||
|     void notify_blink_send(); | ||||
| 
 | ||||
|     /** Get Dialogs instance */ | ||||
|     DialogsApp* get_dialogs(); | ||||
|  | ||||
| @ -51,7 +51,7 @@ bool InfraredAppSceneLearn::on_event(InfraredApp* app, InfraredAppEvent* event) | ||||
|     switch(event->type) { | ||||
|     case InfraredAppEvent::Type::Tick: | ||||
|         consumed = true; | ||||
|         app->notify_red_blink(); | ||||
|         app->notify_blink_read(); | ||||
|         break; | ||||
|     case InfraredAppEvent::Type::InfraredMessageReceived: | ||||
|         app->notify_success(); | ||||
|  | ||||
| @ -95,7 +95,6 @@ bool InfraredAppSceneLearnSuccess::on_event(InfraredApp* app, InfraredAppEvent* | ||||
|         case DialogExPressCenter: | ||||
|             if(!button_pressed) { | ||||
|                 button_pressed = true; | ||||
|                 app->notify_click_and_green_blink(); | ||||
| 
 | ||||
|                 auto signal = app->get_received_signal(); | ||||
|                 if(signal.is_raw()) { | ||||
|  | ||||
| @ -72,14 +72,12 @@ bool InfraredAppSceneRemote::on_event(InfraredApp* app, InfraredAppEvent* event) | ||||
|         switch(event->payload.menu_index) { | ||||
|         case ButtonIndexPlus: | ||||
|             furi_assert(event->type == InfraredAppEvent::Type::MenuSelected); | ||||
|             app->notify_click(); | ||||
|             buttonmenu_item_selected = event->payload.menu_index; | ||||
|             app->set_learn_new_remote(false); | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::Learn); | ||||
|             break; | ||||
|         case ButtonIndexEdit: | ||||
|             furi_assert(event->type == InfraredAppEvent::Type::MenuSelected); | ||||
|             app->notify_click(); | ||||
|             buttonmenu_item_selected = event->payload.menu_index; | ||||
|             app->switch_to_next_scene(InfraredApp::Scene::Edit); | ||||
|             break; | ||||
| @ -89,7 +87,6 @@ bool InfraredAppSceneRemote::on_event(InfraredApp* app, InfraredAppEvent* event) | ||||
| 
 | ||||
|             if(pressed && !button_pressed) { | ||||
|                 button_pressed = true; | ||||
|                 app->notify_click_and_green_blink(); | ||||
| 
 | ||||
|                 auto button_signal = | ||||
|                     app->get_remote_manager()->get_button_data(event->payload.menu_index); | ||||
|  | ||||
| @ -56,6 +56,7 @@ bool InfraredAppSceneUniversalCommon::on_event(InfraredApp* app, InfraredAppEven | ||||
|     if(brute_force_started) { | ||||
|         if(event->type == InfraredAppEvent::Type::Tick) { | ||||
|             auto view_manager = app->get_view_manager(); | ||||
|             app->notify_blink_send(); | ||||
|             InfraredAppEvent tick_event = {.type = InfraredAppEvent::Type::Tick}; | ||||
|             view_manager->send_event(&tick_event); | ||||
|             bool result = brute_force.send_next_bruteforce(); | ||||
| @ -81,6 +82,7 @@ bool InfraredAppSceneUniversalCommon::on_event(InfraredApp* app, InfraredAppEven | ||||
|                 DOLPHIN_DEED(DolphinDeedIrBruteForce); | ||||
|                 brute_force_started = true; | ||||
|                 show_popup(app, record_amount); | ||||
|                 app->notify_blink_send(); | ||||
|             } else { | ||||
|                 app->switch_to_previous_scene(); | ||||
|             } | ||||
|  | ||||
| @ -31,11 +31,9 @@ LfRfidApp::LfRfidApp() | ||||
|     , storage{"storage"} | ||||
|     , dialogs{"dialogs"} | ||||
|     , text_store(40) { | ||||
|     furi_hal_power_insomnia_enter(); | ||||
| } | ||||
| 
 | ||||
| LfRfidApp::~LfRfidApp() { | ||||
|     furi_hal_power_insomnia_exit(); | ||||
| } | ||||
| 
 | ||||
| void LfRfidApp::run(void* _args) { | ||||
| @ -201,4 +199,4 @@ void LfRfidApp::make_app_folder() { | ||||
|     if(!storage_simply_mkdir(storage, app_folder)) { | ||||
|         dialog_message_show_storage_error(dialogs, "Cannot create\napp folder"); | ||||
|     } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -29,7 +29,7 @@ bool LfRfidAppSceneEmulate::on_event(LfRfidApp* app, LfRfidApp::Event* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == LfRfidApp::EventType::Tick) { | ||||
|         notification_message(app->notification, &sequence_blink_cyan_10); | ||||
|         notification_message(app->notification, &sequence_blink_magenta_10); | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
|  | ||||
| @ -24,9 +24,9 @@ bool LfRfidAppSceneRead::on_event(LfRfidApp* app, LfRfidApp::Event* event) { | ||||
|             if(app->worker.any_read()) { | ||||
|                 notification_message(app->notification, &sequence_blink_green_10); | ||||
|             } else if(app->worker.detect()) { | ||||
|                 notification_message(app->notification, &sequence_blink_blue_10); | ||||
|                 notification_message(app->notification, &sequence_blink_cyan_10); | ||||
|             } else { | ||||
|                 notification_message(app->notification, &sequence_blink_red_10); | ||||
|                 notification_message(app->notification, &sequence_blink_cyan_10); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -32,7 +32,7 @@ bool LfRfidAppSceneWrite::on_event(LfRfidApp* app, LfRfidApp::Event* event) { | ||||
| 
 | ||||
|         switch(result) { | ||||
|         case RfidWorker::WriteResult::Nothing: | ||||
|             notification_message(app->notification, &sequence_blink_yellow_10); | ||||
|             notification_message(app->notification, &sequence_blink_magenta_10); | ||||
|             break; | ||||
|         case RfidWorker::WriteResult::Ok: | ||||
|             notification_message(app->notification, &sequence_success); | ||||
| @ -51,7 +51,7 @@ bool LfRfidAppSceneWrite::on_event(LfRfidApp* app, LfRfidApp::Event* event) { | ||||
|                     AlignTop); | ||||
|                 card_not_supported = true; | ||||
|             } | ||||
|             notification_message(app->notification, &sequence_blink_red_10); | ||||
|             notification_message(app->notification, &sequence_blink_yellow_10); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -238,7 +238,10 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con | ||||
|     if(thread_state == FuriThreadStateRunning) { | ||||
|         event.type = LoaderEventTypeApplicationStarted; | ||||
|         furi_pubsub_publish(loader_instance->pubsub, &event); | ||||
|         furi_hal_power_insomnia_enter(); | ||||
| 
 | ||||
|         if(!loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe) { | ||||
|             furi_hal_power_insomnia_enter(); | ||||
|         } | ||||
|     } else if(thread_state == FuriThreadStateStopped) { | ||||
|         FURI_LOG_I( | ||||
|             TAG, | ||||
| @ -251,7 +254,9 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con | ||||
|             loader_instance->application_arguments = NULL; | ||||
|         } | ||||
| 
 | ||||
|         furi_hal_power_insomnia_exit(); | ||||
|         if(!loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe) { | ||||
|             furi_hal_power_insomnia_exit(); | ||||
|         } | ||||
|         loader_unlock(instance); | ||||
| 
 | ||||
|         event.type = LoaderEventTypeApplicationStopped; | ||||
|  | ||||
| @ -262,6 +262,7 @@ static bool nfc_device_save_mifare_df_app(FlipperFormat* file, MifareDesfireAppl | ||||
|                    file, app->key_settings, string_get_cstr(prefix))) | ||||
|                 break; | ||||
|         } | ||||
|         if(!app->file_head) break; | ||||
|         uint32_t n_files = 0; | ||||
|         for(MifareDesfireFile* f = app->file_head; f; f = f->next) { | ||||
|             n_files++; | ||||
| @ -477,6 +478,7 @@ static bool nfc_device_save_mifare_df_data(FlipperFormat* file, NfcDevice* dev) | ||||
|             n_apps++; | ||||
|         } | ||||
|         if(!flipper_format_write_uint32(file, "Application Count", &n_apps, 1)) break; | ||||
|         if(n_apps == 0) break; | ||||
|         tmp = malloc(n_apps * 3); | ||||
|         int i = 0; | ||||
|         for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { | ||||
|  | ||||
| @ -86,7 +86,6 @@ void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state) { | ||||
| int32_t nfc_worker_task(void* context) { | ||||
|     NfcWorker* nfc_worker = context; | ||||
| 
 | ||||
|     furi_hal_power_insomnia_enter(); | ||||
|     furi_hal_nfc_exit_sleep(); | ||||
| 
 | ||||
|     if(nfc_worker->state == NfcWorkerStateDetect) { | ||||
| @ -110,7 +109,6 @@ int32_t nfc_worker_task(void* context) { | ||||
|     } | ||||
|     furi_hal_nfc_sleep(); | ||||
|     nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); | ||||
|     furi_hal_power_insomnia_exit(); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| @ -540,6 +538,7 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { | ||||
|                 FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); | ||||
|                 free(data->master_key_settings); | ||||
|                 data->master_key_settings = NULL; | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             MifareDesfireKeyVersion** key_version_head = | ||||
| @ -593,6 +592,7 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { | ||||
|                     FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); | ||||
|                     free(app->key_settings); | ||||
|                     app->key_settings = NULL; | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 MifareDesfireKeyVersion** key_version_head = &app->key_settings->key_version_head; | ||||
|  | ||||
| @ -50,9 +50,9 @@ typedef enum { | ||||
| 
 | ||||
|     NotificationMessageTypeDelay, | ||||
| 
 | ||||
|     NotificationMessageTypeLedDisplay, | ||||
|     NotificationMessageTypeLedDisplayLock, | ||||
|     NotificationMessageTypeLedDisplayUnlock, | ||||
|     NotificationMessageTypeLedDisplayBacklight, | ||||
|     NotificationMessageTypeLedDisplayBacklightEnforceOn, | ||||
|     NotificationMessageTypeLedDisplayBacklightEnforceAuto, | ||||
| 
 | ||||
|     NotificationMessageTypeDoNotReset, | ||||
| 
 | ||||
|  | ||||
| @ -152,7 +152,7 @@ void notification_sound_off() { | ||||
| static void notification_display_timer(void* ctx) { | ||||
|     furi_assert(ctx); | ||||
|     NotificationApp* app = ctx; | ||||
|     notification_message(app, &sequence_display_off); | ||||
|     notification_message(app, &sequence_display_backlight_off); | ||||
| } | ||||
| 
 | ||||
| // message processing
 | ||||
| @ -174,7 +174,7 @@ void notification_process_notification_message( | ||||
| 
 | ||||
|     while(notification_message != NULL) { | ||||
|         switch(notification_message->type) { | ||||
|         case NotificationMessageTypeLedDisplay: | ||||
|         case NotificationMessageTypeLedDisplayBacklight: | ||||
|             // if on - switch on and start timer
 | ||||
|             // if off - switch off and stop timer
 | ||||
|             // on timer - switch off
 | ||||
| @ -190,7 +190,7 @@ void notification_process_notification_message( | ||||
|             } | ||||
|             reset_mask |= reset_display_mask; | ||||
|             break; | ||||
|         case NotificationMessageTypeLedDisplayLock: | ||||
|         case NotificationMessageTypeLedDisplayBacklightEnforceOn: | ||||
|             furi_assert(app->display_led_lock < UINT8_MAX); | ||||
|             app->display_led_lock++; | ||||
|             if(app->display_led_lock == 1) { | ||||
| @ -199,7 +199,7 @@ void notification_process_notification_message( | ||||
|                     notification_message->data.led.value * display_brightness_setting); | ||||
|             } | ||||
|             break; | ||||
|         case NotificationMessageTypeLedDisplayUnlock: | ||||
|         case NotificationMessageTypeLedDisplayBacklightEnforceAuto: | ||||
|             furi_assert(app->display_led_lock > 0); | ||||
|             app->display_led_lock--; | ||||
|             if(app->display_led_lock == 0) { | ||||
| @ -322,7 +322,7 @@ void notification_process_internal_message(NotificationApp* app, NotificationApp | ||||
| 
 | ||||
|     while(notification_message != NULL) { | ||||
|         switch(notification_message->type) { | ||||
|         case NotificationMessageTypeLedDisplay: | ||||
|         case NotificationMessageTypeLedDisplayBacklight: | ||||
|             notification_apply_internal_led_layer( | ||||
|                 &app->display, | ||||
|                 notification_settings_get_display_brightness( | ||||
| @ -442,7 +442,7 @@ static void input_event_callback(const void* value, void* context) { | ||||
|     furi_assert(value); | ||||
|     furi_assert(context); | ||||
|     NotificationApp* app = context; | ||||
|     notification_message(app, &sequence_display_on); | ||||
|     notification_message(app, &sequence_display_backlight_on); | ||||
| } | ||||
| 
 | ||||
| // App alloc
 | ||||
| @ -482,7 +482,7 @@ static NotificationApp* notification_app_alloc() { | ||||
|     // display backlight control
 | ||||
|     app->event_record = furi_record_open("input_events"); | ||||
|     furi_pubsub_subscribe(app->event_record, input_event_callback, app); | ||||
|     notification_message(app, &sequence_display_on); | ||||
|     notification_message(app, &sequence_display_backlight_on); | ||||
| 
 | ||||
|     return app; | ||||
| }; | ||||
|  | ||||
| @ -4,24 +4,27 @@ | ||||
| 
 | ||||
| /*********************************** Messages **********************************/ | ||||
| 
 | ||||
| // Display
 | ||||
| const NotificationMessage message_display_on = { | ||||
|     .type = NotificationMessageTypeLedDisplay, | ||||
| /** Display: backlight wakeup */ | ||||
| const NotificationMessage message_display_backlight_on = { | ||||
|     .type = NotificationMessageTypeLedDisplayBacklight, | ||||
|     .data.led.value = 0xFF, | ||||
| }; | ||||
| 
 | ||||
| const NotificationMessage message_display_off = { | ||||
|     .type = NotificationMessageTypeLedDisplay, | ||||
| /** Display: backlight force off */ | ||||
| const NotificationMessage message_display_backlight_off = { | ||||
|     .type = NotificationMessageTypeLedDisplayBacklight, | ||||
|     .data.led.value = 0x00, | ||||
| }; | ||||
| 
 | ||||
| const NotificationMessage message_display_lock = { | ||||
|     .type = NotificationMessageTypeLedDisplayLock, | ||||
| /** Display: backlight always on */ | ||||
| const NotificationMessage message_display_backlight_enforce_on = { | ||||
|     .type = NotificationMessageTypeLedDisplayBacklightEnforceOn, | ||||
|     .data.led.value = 0xFF, | ||||
| }; | ||||
| 
 | ||||
| const NotificationMessage message_display_unlock = { | ||||
|     .type = NotificationMessageTypeLedDisplayUnlock, | ||||
| /** Display: automatic backlight management, with configured timeout */ | ||||
| const NotificationMessage message_display_backlight_enforce_auto = { | ||||
|     .type = NotificationMessageTypeLedDisplayBacklightEnforceAuto, | ||||
|     .data.led.value = 0x00, | ||||
| }; | ||||
| 
 | ||||
| @ -166,7 +169,7 @@ const NotificationSequence sequence_reset_rgb = { | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sequence_reset_display = { | ||||
|     &message_display_off, | ||||
|     &message_display_backlight_off, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| @ -188,29 +191,31 @@ const NotificationSequence sequence_set_vibro_on = { | ||||
| }; | ||||
| 
 | ||||
| // Display
 | ||||
| const NotificationSequence sequence_display_on = { | ||||
|     &message_display_on, | ||||
| const NotificationSequence sequence_display_backlight_on = { | ||||
|     &message_display_backlight_on, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sequence_display_off = { | ||||
|     &message_display_off, | ||||
| const NotificationSequence sequence_display_backlight_off = { | ||||
|     &message_display_backlight_off, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sequence_display_lock = { | ||||
|     &message_display_lock, | ||||
| /** Display: backlight always on lock */ | ||||
| const NotificationSequence sequence_display_backlight_enforce_on = { | ||||
|     &message_display_backlight_enforce_on, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sequence_display_unlock = { | ||||
|     &message_display_unlock, | ||||
| /** Display: backlight always on unlock */ | ||||
| const NotificationSequence sequence_display_backlight_enforce_auto = { | ||||
|     &message_display_backlight_enforce_auto, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sequence_display_off_delay_1000 = { | ||||
| const NotificationSequence sequence_display_backlight_off_delay_1000 = { | ||||
|     &message_delay_1000, | ||||
|     &message_display_off, | ||||
|     &message_display_backlight_off, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| @ -309,6 +314,13 @@ const NotificationSequence sequence_blink_cyan_10 = { | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sequence_blink_magenta_10 = { | ||||
|     &message_red_255, | ||||
|     &message_blue_255, | ||||
|     &message_delay_10, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sequence_blink_red_100 = { | ||||
|     &message_red_255, | ||||
|     &message_delay_100, | ||||
| @ -376,7 +388,7 @@ const NotificationSequence sequence_double_vibro = { | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sequence_success = { | ||||
|     &message_display_on, | ||||
|     &message_display_backlight_on, | ||||
|     &message_green_255, | ||||
|     &message_vibro_on, | ||||
|     &message_note_c5, | ||||
| @ -393,7 +405,7 @@ const NotificationSequence sequence_success = { | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sequence_error = { | ||||
|     &message_display_on, | ||||
|     &message_display_backlight_on, | ||||
|     &message_red_255, | ||||
|     &message_vibro_on, | ||||
|     &message_note_c5, | ||||
| @ -415,27 +427,27 @@ const NotificationSequence sequence_audiovisual_alert = { | ||||
|     &message_force_display_brightness_setting_1f, | ||||
|     &message_vibro_on, | ||||
| 
 | ||||
|     &message_display_on, | ||||
|     &message_display_backlight_on, | ||||
|     &message_note_c7, | ||||
|     &message_delay_250, | ||||
| 
 | ||||
|     &message_display_off, | ||||
|     &message_display_backlight_off, | ||||
|     &message_note_c4, | ||||
|     &message_delay_250, | ||||
| 
 | ||||
|     &message_display_on, | ||||
|     &message_display_backlight_on, | ||||
|     &message_note_c7, | ||||
|     &message_delay_250, | ||||
| 
 | ||||
|     &message_display_off, | ||||
|     &message_display_backlight_off, | ||||
|     &message_note_c4, | ||||
|     &message_delay_250, | ||||
| 
 | ||||
|     &message_display_on, | ||||
|     &message_display_backlight_on, | ||||
|     &message_note_c7, | ||||
|     &message_delay_250, | ||||
| 
 | ||||
|     &message_display_off, | ||||
|     &message_display_backlight_off, | ||||
|     &message_note_c4, | ||||
|     &message_delay_250, | ||||
| 
 | ||||
|  | ||||
| @ -9,15 +9,10 @@ extern "C" { | ||||
| /*********************************** Messages **********************************/ | ||||
| 
 | ||||
| // Display
 | ||||
| 
 | ||||
| /** Display: backlight wakeup */ | ||||
| extern const NotificationMessage message_display_on; | ||||
| /** Display: backlight force off */ | ||||
| extern const NotificationMessage message_display_off; | ||||
| /** Display: backlight always on lock */ | ||||
| extern const NotificationMessage message_display_lock; | ||||
| /** Display: backlight always on unlock */ | ||||
| extern const NotificationMessage message_display_unlock; | ||||
| extern const NotificationMessage message_display_backlight_on; | ||||
| extern const NotificationMessage message_display_backlight_off; | ||||
| extern const NotificationMessage message_display_backlight_enforce_on; | ||||
| extern const NotificationMessage message_display_backlight_enforce_auto; | ||||
| 
 | ||||
| // Led ON
 | ||||
| extern const NotificationMessage message_red_255; | ||||
| @ -71,15 +66,16 @@ extern const NotificationSequence sequence_set_vibro_on; | ||||
| 
 | ||||
| // Display
 | ||||
| /** Display: backlight wakeup */ | ||||
| extern const NotificationSequence sequence_display_on; | ||||
| extern const NotificationSequence sequence_display_backlight_on; | ||||
| /** Display: backlight force off */ | ||||
| extern const NotificationSequence sequence_display_off; | ||||
| /** Display: backlight always on lock */ | ||||
| extern const NotificationSequence sequence_display_lock; | ||||
| /** Display: backlight always on unlock */ | ||||
| extern const NotificationSequence sequence_display_unlock; | ||||
| extern const NotificationSequence sequence_display_backlight_off; | ||||
| /** Display: backlight force off after a delay of 1000ms */ | ||||
| extern const NotificationSequence sequence_display_off_delay_1000; | ||||
| extern const NotificationSequence sequence_display_backlight_off_delay_1000; | ||||
| 
 | ||||
| /** Display: backlight always on lock */ | ||||
| extern const NotificationSequence sequence_display_backlight_enforce_on; | ||||
| /** Display: backlight always on unlock */ | ||||
| extern const NotificationSequence sequence_display_backlight_enforce_auto; | ||||
| 
 | ||||
| // Charging
 | ||||
| extern const NotificationSequence sequence_charging; | ||||
| @ -100,6 +96,7 @@ extern const NotificationSequence sequence_blink_red_10; | ||||
| extern const NotificationSequence sequence_blink_green_10; | ||||
| extern const NotificationSequence sequence_blink_yellow_10; | ||||
| extern const NotificationSequence sequence_blink_cyan_10; | ||||
| extern const NotificationSequence sequence_blink_magenta_10; | ||||
| 
 | ||||
| extern const NotificationSequence sequence_blink_red_100; | ||||
| extern const NotificationSequence sequence_blink_green_100; | ||||
|  | ||||
| @ -70,7 +70,7 @@ static void backlight_changed(VariableItem* item) { | ||||
| 
 | ||||
|     variable_item_set_current_value_text(item, backlight_text[index]); | ||||
|     app->notification->settings.display_brightness = backlight_value[index]; | ||||
|     notification_message(app->notification, &sequence_display_on); | ||||
|     notification_message(app->notification, &sequence_display_backlight_on); | ||||
| } | ||||
| 
 | ||||
| static void screen_changed(VariableItem* item) { | ||||
| @ -79,7 +79,7 @@ static void screen_changed(VariableItem* item) { | ||||
| 
 | ||||
|     variable_item_set_current_value_text(item, delay_text[index]); | ||||
|     app->notification->settings.display_off_delay_ms = delay_value[index]; | ||||
|     notification_message(app->notification, &sequence_display_on); | ||||
|     notification_message(app->notification, &sequence_display_backlight_on); | ||||
| } | ||||
| 
 | ||||
| const NotificationMessage apply_message = { | ||||
|  | ||||
| @ -28,7 +28,7 @@ static void battery_test_battery_info_update_model(void* context) { | ||||
|         .health = app->info.health, | ||||
|     }; | ||||
|     battery_info_set_data(app->batery_info, &battery_info_data); | ||||
|     notification_message(app->notifications, &sequence_display_on); | ||||
|     notification_message(app->notifications, &sequence_display_backlight_on); | ||||
| } | ||||
| 
 | ||||
| BatteryTestApp* battery_test_alloc() { | ||||
|  | ||||
| @ -62,8 +62,8 @@ void rpc_cli_command_start_session(Cli* cli, string_t args, void* context) { | ||||
|     size_t size_received = 0; | ||||
| 
 | ||||
|     while(1) { | ||||
|         size_received = furi_hal_vcp_rx_with_timeout(buffer, CLI_READ_BUFFER_SIZE, 50); | ||||
|         if(!furi_hal_vcp_is_connected() || cli_rpc.session_close_request) { | ||||
|         size_received = cli_read_timeout(cli_rpc.cli, buffer, CLI_READ_BUFFER_SIZE, 50); | ||||
|         if(!cli_is_connected(cli_rpc.cli) || cli_rpc.session_close_request) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -6,9 +6,17 @@ | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /** File type */ | ||||
| typedef enum { | ||||
|     FileTypeClosed, /**< Closed file */ | ||||
|     FileTypeOpenDir, /**< Open dir */ | ||||
|     FileTypeOpenFile, /**< Open file */ | ||||
| } FileType; | ||||
| 
 | ||||
| /** Structure that hold file index and returned api errors */ | ||||
| struct File { | ||||
|     uint32_t file_id; /**< File ID for internal references */ | ||||
|     FileType type; | ||||
|     FS_Error error_id; /**< Standart API error from FS_Error enum */ | ||||
|     int32_t internal_error_id; /**< Internal API error value */ | ||||
|     void* storage; | ||||
|  | ||||
| @ -7,6 +7,8 @@ | ||||
| 
 | ||||
| #define MAX_NAME_LENGTH 256 | ||||
| 
 | ||||
| #define TAG "StorageAPI" | ||||
| 
 | ||||
| #define S_API_PROLOGUE                                      \ | ||||
|     osSemaphoreId_t semaphore = osSemaphoreNew(1, 0, NULL); \ | ||||
|     furi_check(semaphore != NULL); | ||||
| @ -47,10 +49,6 @@ | ||||
| #define S_RETURN_ERROR (return_data.error_value); | ||||
| #define S_RETURN_CSTRING (return_data.cstring_value); | ||||
| 
 | ||||
| #define FILE_OPENED_FILE 1 | ||||
| #define FILE_OPENED_DIR 2 | ||||
| #define FILE_CLOSED 0 | ||||
| 
 | ||||
| typedef enum { | ||||
|     StorageEventFlagFileClose = (1 << 0), | ||||
| } StorageEventFlag; | ||||
| @ -72,7 +70,7 @@ static bool storage_file_open_internal( | ||||
|             .open_mode = open_mode, | ||||
|         }}; | ||||
| 
 | ||||
|     file->file_id = FILE_OPENED_FILE; | ||||
|     file->type = FileTypeOpenFile; | ||||
| 
 | ||||
|     S_API_MESSAGE(StorageCommandFileOpen); | ||||
|     S_API_EPILOGUE; | ||||
| @ -113,6 +111,10 @@ bool storage_file_open( | ||||
| 
 | ||||
|     furi_pubsub_unsubscribe(storage_get_pubsub(file->storage), subscription); | ||||
|     osEventFlagsDelete(event); | ||||
| 
 | ||||
|     FURI_LOG_T( | ||||
|         TAG, "File %p - %p open (%s)", (uint32_t)file - SRAM_BASE, file->file_id - SRAM_BASE, path); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| @ -124,7 +126,8 @@ bool storage_file_close(File* file) { | ||||
|     S_API_MESSAGE(StorageCommandFileClose); | ||||
|     S_API_EPILOGUE; | ||||
| 
 | ||||
|     file->file_id = FILE_CLOSED; | ||||
|     FURI_LOG_T(TAG, "File %p - %p closed", (uint32_t)file - SRAM_BASE, file->file_id - SRAM_BASE); | ||||
|     file->type = FileTypeClosed; | ||||
| 
 | ||||
|     return S_RETURN_BOOL; | ||||
| } | ||||
| @ -234,7 +237,7 @@ static bool storage_dir_open_internal(File* file, const char* path) { | ||||
|             .path = path, | ||||
|         }}; | ||||
| 
 | ||||
|     file->file_id = FILE_OPENED_DIR; | ||||
|     file->type = FileTypeOpenDir; | ||||
| 
 | ||||
|     S_API_MESSAGE(StorageCommandDirOpen); | ||||
|     S_API_EPILOGUE; | ||||
| @ -259,6 +262,10 @@ bool storage_dir_open(File* file, const char* path) { | ||||
| 
 | ||||
|     furi_pubsub_unsubscribe(storage_get_pubsub(file->storage), subscription); | ||||
|     osEventFlagsDelete(event); | ||||
| 
 | ||||
|     FURI_LOG_T( | ||||
|         TAG, "Dir %p - %p open (%s)", (uint32_t)file - SRAM_BASE, file->file_id - SRAM_BASE, path); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| @ -269,7 +276,9 @@ bool storage_dir_close(File* file) { | ||||
|     S_API_MESSAGE(StorageCommandDirClose); | ||||
|     S_API_EPILOGUE; | ||||
| 
 | ||||
|     file->file_id = FILE_CLOSED; | ||||
|     FURI_LOG_T(TAG, "Dir %p - %p closed", (uint32_t)file - SRAM_BASE, file->file_id - SRAM_BASE); | ||||
| 
 | ||||
|     file->type = FileTypeClosed; | ||||
| 
 | ||||
|     return S_RETURN_BOOL; | ||||
| } | ||||
| @ -448,18 +457,20 @@ FS_Error storage_sd_status(Storage* storage) { | ||||
| 
 | ||||
| File* storage_file_alloc(Storage* storage) { | ||||
|     File* file = malloc(sizeof(File)); | ||||
|     file->file_id = FILE_CLOSED; | ||||
|     file->type = FileTypeClosed; | ||||
|     file->storage = storage; | ||||
| 
 | ||||
|     FURI_LOG_T(TAG, "File/Dir %p alloc", (uint32_t)file - SRAM_BASE); | ||||
| 
 | ||||
|     return file; | ||||
| } | ||||
| 
 | ||||
| bool storage_file_is_open(File* file) { | ||||
|     return (file->file_id != FILE_CLOSED); | ||||
|     return (file->type != FileTypeClosed); | ||||
| } | ||||
| 
 | ||||
| bool storage_file_is_dir(File* file) { | ||||
|     return (file->file_id == FILE_OPENED_DIR); | ||||
|     return (file->type == FileTypeOpenDir); | ||||
| } | ||||
| 
 | ||||
| void storage_file_free(File* file) { | ||||
| @ -471,6 +482,7 @@ void storage_file_free(File* file) { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     FURI_LOG_T(TAG, "File/Dir %p free", (uint32_t)file - SRAM_BASE); | ||||
|     free(file); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -39,12 +39,10 @@ void storage_data_init(StorageData* storage) { | ||||
| } | ||||
| 
 | ||||
| bool storage_data_lock(StorageData* storage) { | ||||
|     furi_hal_power_insomnia_enter(); | ||||
|     return (osMutexAcquire(storage->mutex, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| bool storage_data_unlock(StorageData* storage) { | ||||
|     furi_hal_power_insomnia_exit(); | ||||
|     return (osMutexRelease(storage->mutex) == osOK); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -12,6 +12,8 @@ struct SubGhzChatWorker { | ||||
|     volatile bool worker_stoping; | ||||
|     osMessageQueueId_t event_queue; | ||||
|     uint32_t last_time_rx_data; | ||||
| 
 | ||||
|     Cli* cli; | ||||
| }; | ||||
| 
 | ||||
| /** Worker thread
 | ||||
| @ -27,7 +29,7 @@ static int32_t subghz_chat_worker_thread(void* context) { | ||||
|     event.event = SubGhzChatEventUserEntrance; | ||||
|     osMessageQueuePut(instance->event_queue, &event, 0, 0); | ||||
|     while(instance->worker_running) { | ||||
|         if(furi_hal_vcp_rx_with_timeout((uint8_t*)&c, 1, 1000) == 1) { | ||||
|         if(cli_read_timeout(instance->cli, (uint8_t*)&c, 1, 1000) == 1) { | ||||
|             event.event = SubGhzChatEventInputData; | ||||
|             event.c = c; | ||||
|             osMessageQueuePut(instance->event_queue, &event, 0, osWaitForever); | ||||
| @ -52,9 +54,11 @@ static void subghz_chat_worker_update_rx_event_chat(void* context) { | ||||
|     osMessageQueuePut(instance->event_queue, &event, 0, osWaitForever); | ||||
| } | ||||
| 
 | ||||
| SubGhzChatWorker* subghz_chat_worker_alloc() { | ||||
| SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli) { | ||||
|     SubGhzChatWorker* instance = malloc(sizeof(SubGhzChatWorker)); | ||||
| 
 | ||||
|     instance->cli = cli; | ||||
| 
 | ||||
|     instance->thread = furi_thread_alloc(); | ||||
|     furi_thread_set_name(instance->thread, "SubGhzChat"); | ||||
|     furi_thread_set_stack_size(instance->thread, 2048); | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| #pragma once | ||||
| #include "../subghz_i.h" | ||||
| #include <cli/cli.h> | ||||
| 
 | ||||
| typedef struct SubGhzChatWorker SubGhzChatWorker; | ||||
| 
 | ||||
| @ -17,7 +18,7 @@ typedef struct { | ||||
|     char c; | ||||
| } SubGhzChatEvent; | ||||
| 
 | ||||
| SubGhzChatWorker* subghz_chat_worker_alloc(); | ||||
| SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli); | ||||
| void subghz_chat_worker_free(SubGhzChatWorker* instance); | ||||
| bool subghz_chat_worker_start(SubGhzChatWorker* instance, uint32_t frequency); | ||||
| void subghz_chat_worker_stop(SubGhzChatWorker* instance); | ||||
|  | ||||
| @ -19,6 +19,8 @@ typedef enum { | ||||
|     SubGhzCustomEventSceneShowErrorOk, | ||||
|     SubGhzCustomEventSceneShowErrorSub, | ||||
|     SubGhzCustomEventSceneShowOnlyRX, | ||||
|     SubGhzCustomEventSceneAnalyzerLock, | ||||
|     SubGhzCustomEventSceneAnalyzerUnlock, | ||||
| 
 | ||||
|     SubGhzCustomEventSceneExit, | ||||
|     SubGhzCustomEventSceneStay, | ||||
| @ -35,7 +37,6 @@ typedef enum { | ||||
|     SubGhzCustomEventViewReadRAWSendStart, | ||||
|     SubGhzCustomEventViewReadRAWSendStop, | ||||
|     SubGhzCustomEventViewReadRAWSave, | ||||
|     SubGhzCustomEventViewReadRAWVibro, | ||||
|     SubGhzCustomEventViewReadRAWTXRXStop, | ||||
|     SubGhzCustomEventViewReadRAWMore, | ||||
| 
 | ||||
|  | ||||
| @ -17,10 +17,20 @@ void subghz_scene_frequency_analyzer_on_enter(void* context) { | ||||
| } | ||||
| 
 | ||||
| bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent event) { | ||||
|     //SubGhz* subghz = context;
 | ||||
|     SubGhz* subghz = context; | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == SubGhzCustomEventSceneAnalyzerLock) { | ||||
|             notification_message(subghz->notifications, &sequence_set_green_255); | ||||
|             return true; | ||||
|         } else if(event.event == SubGhzCustomEventSceneAnalyzerUnlock) { | ||||
|             notification_message(subghz->notifications, &sequence_reset_rgb); | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void subghz_scene_frequency_analyzer_on_exit(void* context) { | ||||
|     // SubGhz* subghz = context;
 | ||||
|     SubGhz* subghz = context; | ||||
|     notification_message(subghz->notifications, &sequence_reset_rgb); | ||||
| } | ||||
|  | ||||
| @ -168,11 +168,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { | ||||
| 
 | ||||
|         case SubGhzCustomEventViewReadRAWErase: | ||||
|             subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; | ||||
|             return true; | ||||
|             break; | ||||
| 
 | ||||
|         case SubGhzCustomEventViewReadRAWVibro: | ||||
|             notification_message(subghz->notifications, &sequence_single_vibro); | ||||
|             notification_message(subghz->notifications, &sequence_reset_rgb); | ||||
|             return true; | ||||
|             break; | ||||
| 
 | ||||
| @ -209,7 +205,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { | ||||
|                             (SubGhzProtocolEncoderRAW*)subghz->txrx->transmitter->protocol_instance, | ||||
|                             subghz_scene_read_raw_callback_end_tx, | ||||
|                             subghz); | ||||
|                         subghz->state_notifications = SubGhzNotificationStateTX; | ||||
|                         subghz->state_notifications = SubGhzNotificationStateTx; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @ -231,6 +227,10 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { | ||||
|                 subghz_rx_end(subghz); | ||||
|                 subghz_sleep(subghz); | ||||
|             }; | ||||
| 
 | ||||
|             size_t spl_count = subghz_protocol_raw_get_sample_write( | ||||
|                 (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result); | ||||
| 
 | ||||
|             subghz_protocol_raw_save_to_file_stop( | ||||
|                 (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result); | ||||
| 
 | ||||
| @ -241,6 +241,12 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { | ||||
|             subghz_protocol_raw_gen_fff_data(subghz->txrx->fff_data, string_get_cstr(temp_str)); | ||||
|             string_clear(temp_str); | ||||
| 
 | ||||
|             if(spl_count > 0) { | ||||
|                 notification_message(subghz->notifications, &sequence_set_green_255); | ||||
|             } else { | ||||
|                 notification_message(subghz->notifications, &sequence_reset_rgb); | ||||
|             } | ||||
| 
 | ||||
|             subghz->state_notifications = SubGhzNotificationStateIDLE; | ||||
|             subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; | ||||
| 
 | ||||
| @ -263,7 +269,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { | ||||
|                         subghz_begin(subghz, subghz->txrx->preset); | ||||
|                         subghz_rx(subghz, subghz->txrx->frequency); | ||||
|                     } | ||||
|                     subghz->state_notifications = SubGhzNotificationStateRX; | ||||
|                     subghz->state_notifications = SubGhzNotificationStateRx; | ||||
|                     subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; | ||||
|                 } else { | ||||
|                     string_set(subghz->error_str, "Function requires\nan SD card."); | ||||
| @ -288,16 +294,16 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeTick) { | ||||
|         switch(subghz->state_notifications) { | ||||
|         case SubGhzNotificationStateRX: | ||||
|             notification_message(subghz->notifications, &sequence_blink_blue_10); | ||||
|         case SubGhzNotificationStateRx: | ||||
|             notification_message(subghz->notifications, &sequence_blink_cyan_10); | ||||
|             subghz_read_raw_update_sample_write( | ||||
|                 subghz->subghz_read_raw, | ||||
|                 subghz_protocol_raw_get_sample_write( | ||||
|                     (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result)); | ||||
|             subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, furi_hal_subghz_get_rssi()); | ||||
|             break; | ||||
|         case SubGhzNotificationStateTX: | ||||
|             notification_message(subghz->notifications, &sequence_blink_green_10); | ||||
|         case SubGhzNotificationStateTx: | ||||
|             notification_message(subghz->notifications, &sequence_blink_magenta_10); | ||||
|             subghz_read_raw_update_sin(subghz->subghz_read_raw); | ||||
|             break; | ||||
|         default: | ||||
| @ -316,6 +322,7 @@ void subghz_scene_read_raw_on_exit(void* context) { | ||||
|         subghz_sleep(subghz); | ||||
|     }; | ||||
|     subghz->state_notifications = SubGhzNotificationStateIDLE; | ||||
|     notification_message(subghz->notifications, &sequence_reset_rgb); | ||||
| 
 | ||||
|     //filter restoration
 | ||||
|     subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); | ||||
|  | ||||
| @ -1,6 +1,19 @@ | ||||
| #include "../subghz_i.h" | ||||
| #include "../views/receiver.h" | ||||
| 
 | ||||
| static const NotificationSequence subghs_sequence_rx = { | ||||
|     &message_green_255, | ||||
| 
 | ||||
|     &message_vibro_on, | ||||
|     &message_note_c6, | ||||
|     &message_delay_50, | ||||
|     &message_sound_off, | ||||
|     &message_vibro_off, | ||||
| 
 | ||||
|     &message_delay_50, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| static void subghz_scene_receiver_update_statusbar(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     string_t history_stat_str; | ||||
| @ -50,6 +63,8 @@ static void subghz_scene_add_to_history_callback( | ||||
|         subghz_receiver_reset(receiver); | ||||
|         string_reset(str_buff); | ||||
| 
 | ||||
|         subghz->state_notifications = SubGhzNotificationStateRxDone; | ||||
| 
 | ||||
|         subghz_history_get_text_item_menu( | ||||
|             subghz->txrx->history, str_buff, subghz_history_get_item(subghz->txrx->history) - 1); | ||||
|         subghz_view_receiver_add_item_to_menu( | ||||
| @ -95,7 +110,7 @@ void subghz_scene_receiver_on_enter(void* context) { | ||||
|     subghz_receiver_set_rx_callback( | ||||
|         subghz->txrx->receiver, subghz_scene_add_to_history_callback, subghz); | ||||
| 
 | ||||
|     subghz->state_notifications = SubGhzNotificationStateRX; | ||||
|     subghz->state_notifications = SubGhzNotificationStateRx; | ||||
|     if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { | ||||
|         subghz_rx_end(subghz); | ||||
|     }; | ||||
| @ -161,8 +176,12 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { | ||||
|         } | ||||
| 
 | ||||
|         switch(subghz->state_notifications) { | ||||
|         case SubGhzNotificationStateRX: | ||||
|             notification_message(subghz->notifications, &sequence_blink_blue_10); | ||||
|         case SubGhzNotificationStateRx: | ||||
|             notification_message(subghz->notifications, &sequence_blink_cyan_10); | ||||
|             break; | ||||
|         case SubGhzNotificationStateRxDone: | ||||
|             notification_message(subghz->notifications, &subghs_sequence_rx); | ||||
|             subghz->state_notifications = SubGhzNotificationStateRx; | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|  | ||||
| @ -126,7 +126,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) | ||||
|                            subghz->txrx->history, subghz->txrx->idx_menu_chosen))) { | ||||
|                     scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); | ||||
|                 } else { | ||||
|                     subghz->state_notifications = SubGhzNotificationStateTX; | ||||
|                     subghz->state_notifications = SubGhzNotificationStateTx; | ||||
|                 } | ||||
|             } | ||||
|             return true; | ||||
| @ -143,7 +143,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) | ||||
|             if(subghz->txrx->hopper_state == SubGhzHopperStatePause) { | ||||
|                 subghz->txrx->hopper_state = SubGhzHopperStateRunnig; | ||||
|             } | ||||
|             subghz->state_notifications = SubGhzNotificationStateRX; | ||||
|             subghz->state_notifications = SubGhzNotificationStateRx; | ||||
|             return true; | ||||
|         } else if(event.event == SubGhzCustomEventSceneReceiverInfoSave) { | ||||
|             //CC1101 Stop RX -> Save
 | ||||
| @ -171,11 +171,15 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) | ||||
|             subghz_hopper_update(subghz); | ||||
|         } | ||||
|         switch(subghz->state_notifications) { | ||||
|         case SubGhzNotificationStateTX: | ||||
|             notification_message(subghz->notifications, &sequence_blink_red_10); | ||||
|         case SubGhzNotificationStateTx: | ||||
|             notification_message(subghz->notifications, &sequence_blink_magenta_10); | ||||
|             break; | ||||
|         case SubGhzNotificationStateRX: | ||||
|             notification_message(subghz->notifications, &sequence_blink_blue_10); | ||||
|         case SubGhzNotificationStateRx: | ||||
|             notification_message(subghz->notifications, &sequence_blink_cyan_10); | ||||
|             break; | ||||
|         case SubGhzNotificationStateRxDone: | ||||
|             notification_message(subghz->notifications, &sequence_blink_green_100); | ||||
|             subghz->state_notifications = SubGhzNotificationStateRx; | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|  | ||||
| @ -4,6 +4,7 @@ | ||||
| #include <dolphin/dolphin.h> | ||||
| #include <flipper_format/flipper_format_i.h> | ||||
| #include <lib/toolbox/stream/stream.h> | ||||
| #include <lib/subghz/protocols/registry.h> | ||||
| 
 | ||||
| #define TAG "SubGhzSetType" | ||||
| 
 | ||||
| @ -19,13 +20,16 @@ enum SubmenuIndex { | ||||
|     SubmenuIndexGateTX, | ||||
|     SubmenuIndexDoorHan_315_00, | ||||
|     SubmenuIndexDoorHan_433_92, | ||||
|     SubmenuIndexFirefly_300_00, | ||||
| }; | ||||
| 
 | ||||
| bool subghz_scene_set_type_submenu_gen_data_protocol( | ||||
|     void* context, | ||||
|     const char* protocol_name, | ||||
|     uint64_t key, | ||||
|     uint32_t bit) { | ||||
|     uint32_t bit, | ||||
|     uint32_t frequency, | ||||
|     FuriHalSubGhzPreset preset) { | ||||
|     furi_assert(context); | ||||
|     SubGhz* subghz = context; | ||||
| 
 | ||||
| @ -44,10 +48,7 @@ bool subghz_scene_set_type_submenu_gen_data_protocol( | ||||
|         Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data); | ||||
|         stream_clean(fff_data_stream); | ||||
|         if(!subghz_protocol_decoder_base_serialize( | ||||
|                subghz->txrx->decoder_result, | ||||
|                subghz->txrx->fff_data, | ||||
|                subghz_setting_get_default_frequency(subghz->setting), | ||||
|                FuriHalSubGhzPresetOok650Async)) { | ||||
|                subghz->txrx->decoder_result, subghz->txrx->fff_data, frequency, preset)) { | ||||
|             FURI_LOG_E(TAG, "Unable to serialize"); | ||||
|             break; | ||||
|         } | ||||
| @ -107,6 +108,12 @@ void subghz_scene_set_type_on_enter(void* context) { | ||||
|         SubmenuIndexCAME24bit, | ||||
|         subghz_scene_set_type_submenu_callback, | ||||
|         subghz); | ||||
|     submenu_add_item( | ||||
|         subghz->submenu, | ||||
|         "Firefly_300", | ||||
|         SubmenuIndexFirefly_300_00, | ||||
|         subghz_scene_set_type_submenu_callback, | ||||
|         subghz); | ||||
|     submenu_add_item( | ||||
|         subghz->submenu, | ||||
|         "CAME TWEE", | ||||
| @ -152,7 +159,13 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { | ||||
|         switch(event.event) { | ||||
|         case SubmenuIndexPricenton: | ||||
|             key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
 | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "Princeton", key, 24)) { | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol( | ||||
|                    subghz, | ||||
|                    SUBGHZ_PROTOCOL_PRINCETON_NAME, | ||||
|                    key, | ||||
|                    24, | ||||
|                    433920000, | ||||
|                    FuriHalSubGhzPresetOok650Async)) { | ||||
|                 uint32_t te = 400; | ||||
|                 flipper_format_update_uint32(subghz->txrx->fff_data, "TE", (uint32_t*)&te, 1); | ||||
|                 generated_protocol = true; | ||||
| @ -160,32 +173,74 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { | ||||
|             break; | ||||
|         case SubmenuIndexNiceFlo12bit: | ||||
|             key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4
 | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "Nice FLO", key, 12)) { | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol( | ||||
|                    subghz, | ||||
|                    SUBGHZ_PROTOCOL_NICE_FLO_NAME, | ||||
|                    key, | ||||
|                    12, | ||||
|                    433920000, | ||||
|                    FuriHalSubGhzPresetOok650Async)) { | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
|         case SubmenuIndexNiceFlo24bit: | ||||
|             key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
 | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "Nice FLO", key, 24)) { | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol( | ||||
|                    subghz, | ||||
|                    SUBGHZ_PROTOCOL_NICE_FLO_NAME, | ||||
|                    key, | ||||
|                    24, | ||||
|                    433920000, | ||||
|                    FuriHalSubGhzPresetOok650Async)) { | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
|         case SubmenuIndexCAME12bit: | ||||
|             key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4
 | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "CAME", key, 12)) { | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol( | ||||
|                    subghz, | ||||
|                    SUBGHZ_PROTOCOL_CAME_NAME, | ||||
|                    key, | ||||
|                    12, | ||||
|                    433920000, | ||||
|                    FuriHalSubGhzPresetOok650Async)) { | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
|         case SubmenuIndexCAME24bit: | ||||
|             key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
 | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "CAME", key, 24)) { | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol( | ||||
|                    subghz, | ||||
|                    SUBGHZ_PROTOCOL_CAME_NAME, | ||||
|                    key, | ||||
|                    24, | ||||
|                    433920000, | ||||
|                    FuriHalSubGhzPresetOok650Async)) { | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
|         case SubmenuIndexFirefly_300_00: | ||||
|             key = (key & 0x3FF); | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol( | ||||
|                    subghz, | ||||
|                    SUBGHZ_PROTOCOL_FIREFLY_NAME, | ||||
|                    key, | ||||
|                    10, | ||||
|                    300000000, | ||||
|                    FuriHalSubGhzPresetOok650Async)) { | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
|         case SubmenuIndexCAMETwee: | ||||
|             key = (key & 0x0FFFFFF0); | ||||
|             key = 0x003FFF7200000000 | (key ^ 0xE0E0E0EE); | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "CAME TWEE", key, 54)) { | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol( | ||||
|                    subghz, | ||||
|                    SUBGHZ_PROTOCOL_CAME_TWEE_NAME, | ||||
|                    key, | ||||
|                    54, | ||||
|                    433920000, | ||||
|                    FuriHalSubGhzPresetOok650Async)) { | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
| @ -198,13 +253,19 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { | ||||
|         case SubmenuIndexGateTX: | ||||
|             key = (key & 0x00F0FF00) | 0xF << 16 | 0x40; //btn 0xF, 0xC, 0xA, 0x6 (?)
 | ||||
|             uint64_t rev_key = subghz_protocol_blocks_reverse_key(key, 24); | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol(subghz, "GateTX", rev_key, 24)) { | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol( | ||||
|                    subghz, | ||||
|                    SUBGHZ_PROTOCOL_GATE_TX_NAME, | ||||
|                    rev_key, | ||||
|                    24, | ||||
|                    433920000, | ||||
|                    FuriHalSubGhzPresetOok650Async)) { | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
|         case SubmenuIndexDoorHan_433_92: | ||||
|             subghz->txrx->transmitter = | ||||
|                 subghz_transmitter_alloc_init(subghz->txrx->environment, "KeeLoq"); | ||||
|             subghz->txrx->transmitter = subghz_transmitter_alloc_init( | ||||
|                 subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); | ||||
|             if(subghz->txrx->transmitter) { | ||||
|                 subghz_protocol_keeloq_create_data( | ||||
|                     subghz->txrx->transmitter->protocol_instance, | ||||
| @ -227,8 +288,8 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { | ||||
|             } | ||||
|             break; | ||||
|         case SubmenuIndexDoorHan_315_00: | ||||
|             subghz->txrx->transmitter = | ||||
|                 subghz_transmitter_alloc_init(subghz->txrx->environment, "KeeLoq"); | ||||
|             subghz->txrx->transmitter = subghz_transmitter_alloc_init( | ||||
|                 subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); | ||||
|             if(subghz->txrx->transmitter) { | ||||
|                 subghz_protocol_keeloq_create_data( | ||||
|                     subghz->txrx->transmitter->protocol_instance, | ||||
|  | ||||
| @ -1,6 +1,13 @@ | ||||
| #include "../subghz_i.h" | ||||
| #include "../helpers/subghz_custom_event.h" | ||||
| 
 | ||||
| static const NotificationSequence subghs_sequence_sd_error = { | ||||
|     &message_red_255, | ||||
|     &message_green_255, | ||||
|     &message_do_not_reset, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| void subghz_scene_show_error_callback(GuiButtonType result, InputType type, void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhz* subghz = context; | ||||
| @ -31,6 +38,8 @@ void subghz_scene_show_error_on_enter(void* context) { | ||||
|        SubGhzCustomEventManagerSet) { | ||||
|         widget_add_button_element( | ||||
|             subghz->widget, GuiButtonTypeRight, "Ok", subghz_scene_show_error_callback, subghz); | ||||
|     } else { | ||||
|         notification_message(subghz->notifications, &subghs_sequence_sd_error); | ||||
|     } | ||||
| 
 | ||||
|     widget_add_button_element( | ||||
| @ -81,4 +90,5 @@ void subghz_scene_show_error_on_exit(void* context) { | ||||
|         subghz->scene_manager, SubGhzSceneShowError, SubGhzCustomEventManagerNoSet); | ||||
|     widget_reset(subghz->widget); | ||||
|     string_reset(subghz->error_str); | ||||
|     notification_message(subghz->notifications, &sequence_reset_rgb); | ||||
| } | ||||
|  | ||||
| @ -18,6 +18,8 @@ void subghz_scene_show_error_sub_on_enter(void* context) { | ||||
|     popup_set_callback(popup, subghz_scene_show_error_sub_popup_callback); | ||||
|     popup_enable_timeout(popup); | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); | ||||
| 
 | ||||
|     notification_message(subghz->notifications, &sequence_set_red_255); | ||||
| } | ||||
| 
 | ||||
| bool subghz_scene_show_error_sub_on_event(void* context, SceneManagerEvent event) { | ||||
| @ -45,4 +47,6 @@ void subghz_scene_show_error_sub_on_exit(void* context) { | ||||
|     popup_set_timeout(popup, 0); | ||||
|     popup_disable_timeout(popup); | ||||
|     string_reset(subghz->error_str); | ||||
| 
 | ||||
|     notification_message(subghz->notifications, &sequence_reset_rgb); | ||||
| } | ||||
|  | ||||
| @ -76,7 +76,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { | ||||
|                 if(!subghz_tx_start(subghz, subghz->txrx->fff_data)) { | ||||
|                     scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); | ||||
|                 } else { | ||||
|                     subghz->state_notifications = SubGhzNotificationStateTX; | ||||
|                     subghz->state_notifications = SubGhzNotificationStateTx; | ||||
|                     subghz_scene_transmitter_update_data_show(subghz); | ||||
|                 } | ||||
|             } | ||||
| @ -98,8 +98,8 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { | ||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeTick) { | ||||
|         if(subghz->state_notifications == SubGhzNotificationStateTX) { | ||||
|             notification_message(subghz->notifications, &sequence_blink_red_10); | ||||
|         if(subghz->state_notifications == SubGhzNotificationStateTx) { | ||||
|             notification_message(subghz->notifications, &sequence_blink_magenta_10); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
| @ -525,7 +525,7 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(); | ||||
|     SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(cli); | ||||
|     if(!subghz_chat_worker_start(subghz_chat, frequency)) { | ||||
|         printf("Startup error SubGhzChatWorker\r\n"); | ||||
| 
 | ||||
|  | ||||
| @ -41,8 +41,9 @@ | ||||
| typedef enum { | ||||
|     SubGhzNotificationStateStarting, | ||||
|     SubGhzNotificationStateIDLE, | ||||
|     SubGhzNotificationStateTX, | ||||
|     SubGhzNotificationStateRX, | ||||
|     SubGhzNotificationStateTx, | ||||
|     SubGhzNotificationStateRx, | ||||
|     SubGhzNotificationStateRxDone, | ||||
| } SubGhzNotificationState; | ||||
| 
 | ||||
| /** SubGhzTxRx state */ | ||||
|  | ||||
| @ -19,6 +19,7 @@ struct SubGhzFrequencyAnalyzer { | ||||
|     SubGhzFrequencyAnalyzerWorker* worker; | ||||
|     SubGhzFrequencyAnalyzerCallback callback; | ||||
|     void* context; | ||||
|     bool locked; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -86,6 +87,17 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { | ||||
| 
 | ||||
| void subghz_frequency_analyzer_pair_callback(void* context, uint32_t frequency, float rssi) { | ||||
|     SubGhzFrequencyAnalyzer* instance = context; | ||||
|     if((rssi == 0.f) && (instance->locked)) { | ||||
|         if(instance->callback) { | ||||
|             instance->callback(SubGhzCustomEventSceneAnalyzerUnlock, instance->context); | ||||
|         } | ||||
|     } else if((rssi != 0.f) && (!instance->locked)) { | ||||
|         if(instance->callback) { | ||||
|             instance->callback(SubGhzCustomEventSceneAnalyzerLock, instance->context); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     instance->locked = (rssi != 0.f); | ||||
|     with_view_model( | ||||
|         instance->view, (SubGhzFrequencyAnalyzerModel * model) { | ||||
|             model->rssi = rssi; | ||||
|  | ||||
| @ -293,7 +293,6 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { | ||||
|                 case SubGhzReadRAWStatusIDLE: | ||||
|                     // Start TX
 | ||||
|                     instance->callback(SubGhzCustomEventViewReadRAWSendStart, instance->context); | ||||
|                     instance->callback(SubGhzCustomEventViewReadRAWVibro, instance->context); | ||||
|                     model->satus = SubGhzReadRAWStatusTXRepeat; | ||||
|                     ret = true; | ||||
|                     break; | ||||
| @ -304,7 +303,6 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { | ||||
|                 case SubGhzReadRAWStatusLoadKeyIDLE: | ||||
|                     // Start Load Key TX
 | ||||
|                     instance->callback(SubGhzCustomEventViewReadRAWSendStart, instance->context); | ||||
|                     instance->callback(SubGhzCustomEventViewReadRAWVibro, instance->context); | ||||
|                     model->satus = SubGhzReadRAWStatusLoadKeyTXRepeat; | ||||
|                     ret = true; | ||||
|                     break; | ||||
|  | ||||
| @ -59,18 +59,20 @@ bool u2f_scene_main_on_event(void* context, SceneManagerEvent event) { | ||||
|                     u2f_view_set_state(app->u2f_view, U2fMsgRegister); | ||||
|                 else if(event.event == U2fCustomEventAuth) | ||||
|                     u2f_view_set_state(app->u2f_view, U2fMsgAuth); | ||||
|                 notification_message(app->notifications, &sequence_display_on); | ||||
|                 notification_message(app->notifications, &sequence_display_backlight_on); | ||||
|                 notification_message(app->notifications, &sequence_single_vibro); | ||||
|             } | ||||
|             notification_message(app->notifications, &sequence_blink_blue_10); | ||||
|             notification_message(app->notifications, &sequence_blink_magenta_10); | ||||
|         } else if(event.event == U2fCustomEventWink) { | ||||
|             notification_message(app->notifications, &sequence_blink_green_10); | ||||
|             notification_message(app->notifications, &sequence_blink_magenta_10); | ||||
|         } else if(event.event == U2fCustomEventAuthSuccess) { | ||||
|             notification_message_block(app->notifications, &sequence_set_green_255); | ||||
|             DOLPHIN_DEED(DolphinDeedU2fAuthorized); | ||||
|             osTimerStart(app->timer, U2F_SUCCESS_TIMEOUT); | ||||
|             app->event_cur = U2fCustomEventNone; | ||||
|             u2f_view_set_state(app->u2f_view, U2fMsgSuccess); | ||||
|         } else if(event.event == U2fCustomEventTimeout) { | ||||
|             notification_message_block(app->notifications, &sequence_reset_rgb); | ||||
|             app->event_cur = U2fCustomEventNone; | ||||
|             u2f_view_set_state(app->u2f_view, U2fMsgIdle); | ||||
|         } else if(event.event == U2fCustomEventConfirm) { | ||||
| @ -78,6 +80,7 @@ bool u2f_scene_main_on_event(void* context, SceneManagerEvent event) { | ||||
|                 u2f_confirm_user_present(app->u2f_instance); | ||||
|             } | ||||
|         } else if(event.event == U2fCustomEventDataError) { | ||||
|             notification_message(app->notifications, &sequence_set_red_255); | ||||
|             osTimerStop(app->timer); | ||||
|             u2f_view_set_state(app->u2f_view, U2fMsgError); | ||||
|         } | ||||
| @ -108,6 +111,7 @@ void u2f_scene_main_on_enter(void* context) { | ||||
| 
 | ||||
| void u2f_scene_main_on_exit(void* context) { | ||||
|     U2fApp* app = context; | ||||
|     notification_message_block(app->notifications, &sequence_reset_rgb); | ||||
|     osTimerStop(app->timer); | ||||
|     osTimerDelete(app->timer); | ||||
|     if(app->u2f_ready == true) { | ||||
|  | ||||
| @ -25,6 +25,7 @@ static void sd_mount_callback(const void* message, void* context) { | ||||
| 
 | ||||
| void updater_scene_main_on_enter(void* context) { | ||||
|     Updater* updater = (Updater*)context; | ||||
|     notification_message(updater->notification, &sequence_display_backlight_enforce_on); | ||||
|     UpdaterMainView* main_view = updater->main_view; | ||||
| 
 | ||||
|     FuriPubSubSubscription* sub = | ||||
| @ -36,9 +37,9 @@ void updater_scene_main_on_enter(void* context) { | ||||
|     * will be missing from UI, however, /ext will be fully operational. So, until it's fixed, this | ||||
|     * should remain commented out. */ | ||||
|     // If (somehow) we started after SD card is mounted, initiate update immediately
 | ||||
|     //if(storage_sd_status(updater->storage) == FSE_OK) {
 | ||||
|     //    view_dispatcher_send_custom_event(updater->view_dispatcher, UpdaterCustomEventStartUpdate);
 | ||||
|     //}
 | ||||
|     if(storage_sd_status(updater->storage) == FSE_OK) { | ||||
|         view_dispatcher_send_custom_event(updater->view_dispatcher, UpdaterCustomEventStartUpdate); | ||||
|     } | ||||
| 
 | ||||
|     updater_main_set_view_dispatcher(main_view, updater->view_dispatcher); | ||||
|     view_dispatcher_switch_to_view(updater->view_dispatcher, UpdaterViewMain); | ||||
| @ -64,13 +65,6 @@ bool updater_scene_main_on_event(void* context, SceneManagerEvent event) { | ||||
|     } else if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case UpdaterCustomEventStartUpdate: | ||||
|             if(!update_task_is_running(updater->update_task) && | ||||
|                update_task_init(updater->update_task)) { | ||||
|                 update_task_start(updater->update_task); | ||||
|             } | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         case UpdaterCustomEventRetryUpdate: | ||||
|             if(!update_task_is_running(updater->update_task) && | ||||
|                (update_task_get_state(updater->update_task)->stage != UpdateTaskStageCompleted)) | ||||
| @ -99,8 +93,9 @@ bool updater_scene_main_on_event(void* context, SceneManagerEvent event) { | ||||
| void updater_scene_main_on_exit(void* context) { | ||||
|     Updater* updater = (Updater*)context; | ||||
| 
 | ||||
|     notification_message(updater->notification, &sequence_display_backlight_enforce_auto); | ||||
|     furi_pubsub_unsubscribe( | ||||
|         storage_get_pubsub(updater->storage), updater_main_get_storage_pubsub(updater->main_view)); | ||||
| 
 | ||||
|     scene_manager_set_scene_state(updater->scene_manager, UpdaterSceneMain, 0); | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -47,6 +47,7 @@ Updater* updater_alloc(const char* arg) { | ||||
|     } | ||||
| 
 | ||||
|     updater->storage = furi_record_open("storage"); | ||||
|     updater->notification = furi_record_open("notification"); | ||||
| 
 | ||||
|     updater->gui = furi_record_open("gui"); | ||||
|     updater->view_dispatcher = view_dispatcher_alloc(); | ||||
| @ -119,6 +120,7 @@ void updater_free(Updater* updater) { | ||||
| 
 | ||||
|     furi_record_close("gui"); | ||||
|     furi_record_close("storage"); | ||||
|     furi_record_close("notification"); | ||||
| 
 | ||||
|     free(updater); | ||||
| } | ||||
|  | ||||
| @ -11,6 +11,7 @@ | ||||
| #include <gui/scene_manager.h> | ||||
| #include <gui/modules/widget.h> | ||||
| #include <storage/storage.h> | ||||
| #include <notification/notification_app.h> | ||||
| #include <update_util/update_operation.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| @ -41,6 +42,7 @@ typedef struct UpdaterManifestProcessingState { | ||||
| typedef struct { | ||||
|     // GUI
 | ||||
|     Gui* gui; | ||||
|     NotificationApp* notification; | ||||
|     SceneManager* scene_manager; | ||||
|     ViewDispatcher* view_dispatcher; | ||||
|     Storage* storage; | ||||
|  | ||||
| @ -15,13 +15,18 @@ static const char* update_task_stage_descr[] = { | ||||
|     [UpdateTaskStageValidateDFUImage] = "Checking DFU file", | ||||
|     [UpdateTaskStageFlashWrite] = "Writing flash", | ||||
|     [UpdateTaskStageFlashValidate] = "Validating", | ||||
|     [UpdateTaskStageRadioImageValidate] = "Checking radio image", | ||||
|     [UpdateTaskStageRadioErase] = "Removing radio stack", | ||||
|     [UpdateTaskStageRadioWrite] = "Writing radio stack", | ||||
|     [UpdateTaskStageRadioCommit] = "Applying radio stack", | ||||
|     [UpdateTaskStageRadioInstall] = "Installing radio stack", | ||||
|     [UpdateTaskStageRadioBusy] = "Core2 is updating", | ||||
|     [UpdateTaskStageOBValidation] = "Validating opt. bytes", | ||||
|     [UpdateTaskStageLfsBackup] = "Backing up LFS", | ||||
|     [UpdateTaskStageLfsRestore] = "Restoring LFS", | ||||
|     [UpdateTaskStageResourcesUpdate] = "Updating resources", | ||||
|     [UpdateTaskStageCompleted] = "Completed!", | ||||
|     [UpdateTaskStageError] = "Error", | ||||
|     [UpdateTaskStageOBError] = "OB error, pls report", | ||||
| }; | ||||
| 
 | ||||
| static void update_task_set_status(UpdateTask* update_task, const char* status) { | ||||
| @ -37,7 +42,10 @@ static void update_task_set_status(UpdateTask* update_task, const char* status) | ||||
| 
 | ||||
| void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress) { | ||||
|     if(stage != UpdateTaskStageProgress) { | ||||
|         update_task->state.stage = stage; | ||||
|         // do not override more specific error states
 | ||||
|         if((update_task->state.stage < UpdateTaskStageError) || (stage < UpdateTaskStageError)) { | ||||
|             update_task->state.stage = stage; | ||||
|         } | ||||
|         update_task->state.current_stage_idx++; | ||||
|         update_task_set_status(update_task, NULL); | ||||
|     } | ||||
| @ -53,7 +61,7 @@ void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, ui | ||||
|             progress, | ||||
|             update_task->state.current_stage_idx, | ||||
|             update_task->state.total_stages, | ||||
|             update_task->state.stage == UpdateTaskStageError, | ||||
|             update_task->state.stage >= UpdateTaskStageError, | ||||
|             update_task->status_change_cb_state); | ||||
|     } | ||||
| } | ||||
| @ -116,6 +124,7 @@ UpdateTask* update_task_alloc() { | ||||
|     update_task->storage = furi_record_open("storage"); | ||||
|     update_task->file = storage_file_alloc(update_task->storage); | ||||
|     update_task->status_change_cb = NULL; | ||||
|     string_init(update_task->update_path); | ||||
| 
 | ||||
|     FuriThread* thread = update_task->thread = furi_thread_alloc(); | ||||
| 
 | ||||
| @ -152,12 +161,6 @@ void update_task_free(UpdateTask* update_task) { | ||||
|     free(update_task); | ||||
| } | ||||
| 
 | ||||
| bool update_task_init(UpdateTask* update_task) { | ||||
|     furi_assert(update_task); | ||||
|     string_init(update_task->update_path); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool update_task_parse_manifest(UpdateTask* update_task) { | ||||
|     furi_assert(update_task); | ||||
|     update_task_set_progress(update_task, UpdateTaskStageReadManifest, 0); | ||||
| @ -224,4 +227,4 @@ UpdateTaskState const* update_task_get_state(UpdateTask* update_task) { | ||||
| UpdateManifest const* update_task_get_manifest(UpdateTask* update_task) { | ||||
|     furi_assert(update_task); | ||||
|     return update_task->manifest; | ||||
| } | ||||
| } | ||||
| @ -19,13 +19,18 @@ typedef enum { | ||||
|     UpdateTaskStageValidateDFUImage, | ||||
|     UpdateTaskStageFlashWrite, | ||||
|     UpdateTaskStageFlashValidate, | ||||
|     UpdateTaskStageRadioImageValidate, | ||||
|     UpdateTaskStageRadioErase, | ||||
|     UpdateTaskStageRadioWrite, | ||||
|     UpdateTaskStageRadioCommit, | ||||
|     UpdateTaskStageRadioInstall, | ||||
|     UpdateTaskStageRadioBusy, | ||||
|     UpdateTaskStageOBValidation, | ||||
|     UpdateTaskStageLfsBackup, | ||||
|     UpdateTaskStageLfsRestore, | ||||
|     UpdateTaskStageResourcesUpdate, | ||||
|     UpdateTaskStageCompleted, | ||||
|     UpdateTaskStageError, | ||||
|     UpdateTaskStageOBError | ||||
| } UpdateTaskStage; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -50,8 +55,6 @@ UpdateTask* update_task_alloc(); | ||||
| 
 | ||||
| void update_task_free(UpdateTask* update_task); | ||||
| 
 | ||||
| bool update_task_init(UpdateTask* update_task); | ||||
| 
 | ||||
| void update_task_set_progress_cb(UpdateTask* update_task, updateProgressCb cb, void* state); | ||||
| 
 | ||||
| bool update_task_start(UpdateTask* update_task); | ||||
|  | ||||
| @ -9,99 +9,17 @@ | ||||
| #include <update_util/lfs_backup.h> | ||||
| #include <update_util/update_operation.h> | ||||
| #include <toolbox/tar/tar_archive.h> | ||||
| #include <toolbox/crc32_calc.h> | ||||
| 
 | ||||
| #define TAG "UpdWorkerBackup" | ||||
| 
 | ||||
| #define CHECK_RESULT(x) \ | ||||
|     if(!(x)) {          \ | ||||
|         break;          \ | ||||
|     } | ||||
| 
 | ||||
| #define STM_DFU_VENDOR_ID 0x0483 | ||||
| #define STM_DFU_PRODUCT_ID 0xDF11 | ||||
| /* Written into DFU file by build pipeline */ | ||||
| #define FLIPPER_ZERO_DFU_DEVICE_CODE 0xFFFF | ||||
| 
 | ||||
| #define EXT_PATH "/ext" | ||||
| 
 | ||||
| static const DfuValidationParams flipper_dfu_params = { | ||||
|     .device = FLIPPER_ZERO_DFU_DEVICE_CODE, | ||||
|     .product = STM_DFU_PRODUCT_ID, | ||||
|     .vendor = STM_DFU_VENDOR_ID, | ||||
| }; | ||||
| 
 | ||||
| static void update_task_dfu_progress(const uint8_t progress, void* context) { | ||||
|     UpdateTask* update_task = context; | ||||
|     update_task_set_progress(update_task, UpdateTaskStageProgress, progress); | ||||
| } | ||||
| 
 | ||||
| static bool page_task_compare_flash( | ||||
|     const uint8_t i_page, | ||||
|     const uint8_t* update_block, | ||||
|     uint16_t update_block_len) { | ||||
|     const size_t page_addr = furi_hal_flash_get_base() + furi_hal_flash_get_page_size() * i_page; | ||||
|     return (memcmp(update_block, (void*)page_addr, update_block_len) == 0); | ||||
| } | ||||
| 
 | ||||
| /* Verifies a flash operation address for fitting into writable memory
 | ||||
|  */ | ||||
| static bool check_address_boundaries(const size_t address) { | ||||
|     const size_t min_allowed_address = furi_hal_flash_get_base(); | ||||
|     const size_t max_allowed_address = (size_t)furi_hal_flash_get_free_end_address(); | ||||
|     return ((address >= min_allowed_address) && (address < max_allowed_address)); | ||||
| } | ||||
| 
 | ||||
| int32_t update_task_worker_flash_writer(void* context) { | ||||
|     furi_assert(context); | ||||
|     UpdateTask* update_task = context; | ||||
|     bool success = false; | ||||
|     DfuUpdateTask page_task = { | ||||
|         .address_cb = &check_address_boundaries, | ||||
|         .progress_cb = &update_task_dfu_progress, | ||||
|         .task_cb = &furi_hal_flash_program_page, | ||||
|         .context = update_task, | ||||
|     }; | ||||
| 
 | ||||
|     update_task->state.current_stage_idx = 0; | ||||
|     update_task->state.total_stages = 4; | ||||
| 
 | ||||
|     do { | ||||
|         CHECK_RESULT(update_task_parse_manifest(update_task)); | ||||
| 
 | ||||
|         if(!string_empty_p(update_task->manifest->firmware_dfu_image)) { | ||||
|             update_task_set_progress(update_task, UpdateTaskStageValidateDFUImage, 0); | ||||
|             CHECK_RESULT( | ||||
|                 update_task_open_file(update_task, update_task->manifest->firmware_dfu_image)); | ||||
|             CHECK_RESULT( | ||||
|                 dfu_file_validate_crc(update_task->file, &update_task_dfu_progress, update_task)); | ||||
| 
 | ||||
|             const uint8_t valid_targets = | ||||
|                 dfu_file_validate_headers(update_task->file, &flipper_dfu_params); | ||||
|             if(valid_targets == 0) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             update_task_set_progress(update_task, UpdateTaskStageFlashWrite, 0); | ||||
|             CHECK_RESULT(dfu_file_process_targets(&page_task, update_task->file, valid_targets)); | ||||
| 
 | ||||
|             page_task.task_cb = &page_task_compare_flash; | ||||
| 
 | ||||
|             update_task_set_progress(update_task, UpdateTaskStageFlashValidate, 0); | ||||
|             CHECK_RESULT(dfu_file_process_targets(&page_task, update_task->file, valid_targets)); | ||||
|         } | ||||
| 
 | ||||
|         update_task_set_progress(update_task, UpdateTaskStageCompleted, 100); | ||||
| 
 | ||||
|         furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); | ||||
| 
 | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     if(!success) { | ||||
|         update_task_set_progress(update_task, UpdateTaskStageError, update_task->state.progress); | ||||
|     } | ||||
| 
 | ||||
|     return success ? UPDATE_TASK_NOERR : UPDATE_TASK_FAILED; | ||||
| } | ||||
| 
 | ||||
| static bool update_task_pre_update(UpdateTask* update_task) { | ||||
|     bool success = false; | ||||
|     string_t backup_file_path; | ||||
| @ -111,7 +29,8 @@ static bool update_task_pre_update(UpdateTask* update_task) { | ||||
| 
 | ||||
|     update_task->state.total_stages = 1; | ||||
|     update_task_set_progress(update_task, UpdateTaskStageLfsBackup, 0); | ||||
|     furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal); // to avoid bootloops
 | ||||
|     /* to avoid bootloops */ | ||||
|     furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal); | ||||
|     if((success = lfs_backup_create(update_task->storage, string_get_cstr(backup_file_path)))) { | ||||
|         furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeUpdate); | ||||
|     } | ||||
| @ -143,7 +62,8 @@ static bool update_task_post_update(UpdateTask* update_task) { | ||||
|     string_t file_path; | ||||
|     string_init(file_path); | ||||
| 
 | ||||
|     update_task->state.total_stages = 2; | ||||
|     /* status text is too long, too few stages to bother with a counter */ | ||||
|     update_task->state.total_stages = 0; | ||||
| 
 | ||||
|     do { | ||||
|         CHECK_RESULT(update_task_parse_manifest(update_task)); | ||||
| @ -151,9 +71,6 @@ static bool update_task_post_update(UpdateTask* update_task) { | ||||
|             string_get_cstr(update_task->update_path), LFS_BACKUP_DEFAULT_FILENAME, file_path); | ||||
| 
 | ||||
|         bool unpack_resources = !string_empty_p(update_task->manifest->resource_bundle); | ||||
|         if(unpack_resources) { | ||||
|             update_task->state.total_stages++; | ||||
|         } | ||||
| 
 | ||||
|         update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0); | ||||
|         furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal); | ||||
| @ -184,6 +101,7 @@ static bool update_task_post_update(UpdateTask* update_task) { | ||||
|             } | ||||
|             tar_archive_free(archive); | ||||
|         } | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     string_clear(file_path); | ||||
| @ -197,7 +115,7 @@ int32_t update_task_worker_backup_restore(void* context) { | ||||
| 
 | ||||
|     FuriHalRtcBootMode boot_mode = furi_hal_rtc_get_boot_mode(); | ||||
|     if((boot_mode != FuriHalRtcBootModePreUpdate) && (boot_mode != FuriHalRtcBootModePostUpdate)) { | ||||
|         // no idea how we got here. Clear to normal boot
 | ||||
|         /* no idea how we got here. Clear to normal boot */ | ||||
|         furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal); | ||||
|         return UPDATE_TASK_NOERR; | ||||
|     } | ||||
| @ -208,12 +126,17 @@ int32_t update_task_worker_backup_restore(void* context) { | ||||
|         return UPDATE_TASK_FAILED; | ||||
|     } | ||||
| 
 | ||||
|     /* Waiting for BT service to 'start', so we don't race for boot mode */ | ||||
|     furi_record_open("bt"); | ||||
| 
 | ||||
|     if(boot_mode == FuriHalRtcBootModePreUpdate) { | ||||
|         success = update_task_pre_update(update_task); | ||||
|     } else if(boot_mode == FuriHalRtcBootModePostUpdate) { | ||||
|         success = update_task_post_update(update_task); | ||||
|     } | ||||
| 
 | ||||
|     furi_record_close("bt"); | ||||
| 
 | ||||
|     if(success) { | ||||
|         update_task_set_progress(update_task, UpdateTaskStageCompleted, 100); | ||||
|     } else { | ||||
| @ -221,4 +144,4 @@ int32_t update_task_worker_backup_restore(void* context) { | ||||
|     } | ||||
| 
 | ||||
|     return success ? UPDATE_TASK_NOERR : UPDATE_TASK_FAILED; | ||||
| } | ||||
| } | ||||
							
								
								
									
										363
									
								
								applications/updater/util/update_task_worker_flasher.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										363
									
								
								applications/updater/util/update_task_worker_flasher.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,363 @@ | ||||
| #include "update_task.h" | ||||
| #include "update_task_i.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <storage/storage.h> | ||||
| #include <toolbox/path.h> | ||||
| #include <update_util/dfu_file.h> | ||||
| #include <update_util/lfs_backup.h> | ||||
| #include <update_util/update_operation.h> | ||||
| #include <toolbox/tar/tar_archive.h> | ||||
| #include <toolbox/crc32_calc.h> | ||||
| 
 | ||||
| #define TAG "UpdWorkerRAM" | ||||
| 
 | ||||
| #define CHECK_RESULT(x) \ | ||||
|     if(!(x)) {          \ | ||||
|         break;          \ | ||||
|     } | ||||
| 
 | ||||
| #define STM_DFU_VENDOR_ID 0x0483 | ||||
| #define STM_DFU_PRODUCT_ID 0xDF11 | ||||
| /* Written into DFU file by build pipeline */ | ||||
| #define FLIPPER_ZERO_DFU_DEVICE_CODE 0xFFFF | ||||
| /* Time, in ms, to wait for system restart by C2 before crashing */ | ||||
| #define C2_MODE_SWITCH_TIMEOUT 10000 | ||||
| 
 | ||||
| static const DfuValidationParams flipper_dfu_params = { | ||||
|     .device = FLIPPER_ZERO_DFU_DEVICE_CODE, | ||||
|     .product = STM_DFU_PRODUCT_ID, | ||||
|     .vendor = STM_DFU_VENDOR_ID, | ||||
| }; | ||||
| 
 | ||||
| static void update_task_file_progress(const uint8_t progress, void* context) { | ||||
|     UpdateTask* update_task = context; | ||||
|     update_task_set_progress(update_task, UpdateTaskStageProgress, progress); | ||||
| } | ||||
| 
 | ||||
| static bool page_task_compare_flash( | ||||
|     const uint8_t i_page, | ||||
|     const uint8_t* update_block, | ||||
|     uint16_t update_block_len) { | ||||
|     const size_t page_addr = furi_hal_flash_get_base() + furi_hal_flash_get_page_size() * i_page; | ||||
|     return (memcmp(update_block, (void*)page_addr, update_block_len) == 0); | ||||
| } | ||||
| 
 | ||||
| /* Verifies a flash operation address for fitting into writable memory
 | ||||
|  */ | ||||
| static bool check_address_boundaries(const size_t address) { | ||||
|     const size_t min_allowed_address = furi_hal_flash_get_base(); | ||||
|     const size_t max_allowed_address = (size_t)furi_hal_flash_get_free_end_address(); | ||||
|     return ((address >= min_allowed_address) && (address < max_allowed_address)); | ||||
| } | ||||
| 
 | ||||
| static bool update_task_write_dfu(UpdateTask* update_task) { | ||||
|     DfuUpdateTask page_task = { | ||||
|         .address_cb = &check_address_boundaries, | ||||
|         .progress_cb = &update_task_file_progress, | ||||
|         .task_cb = &furi_hal_flash_program_page, | ||||
|         .context = update_task, | ||||
|     }; | ||||
| 
 | ||||
|     bool success = false; | ||||
|     do { | ||||
|         update_task_set_progress(update_task, UpdateTaskStageValidateDFUImage, 0); | ||||
|         CHECK_RESULT( | ||||
|             update_task_open_file(update_task, update_task->manifest->firmware_dfu_image)); | ||||
|         CHECK_RESULT( | ||||
|             dfu_file_validate_crc(update_task->file, &update_task_file_progress, update_task)); | ||||
| 
 | ||||
|         const uint8_t valid_targets = | ||||
|             dfu_file_validate_headers(update_task->file, &flipper_dfu_params); | ||||
|         if(valid_targets == 0) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         update_task_set_progress(update_task, UpdateTaskStageFlashWrite, 0); | ||||
|         CHECK_RESULT(dfu_file_process_targets(&page_task, update_task->file, valid_targets)); | ||||
| 
 | ||||
|         page_task.task_cb = &page_task_compare_flash; | ||||
| 
 | ||||
|         update_task_set_progress(update_task, UpdateTaskStageFlashValidate, 0); | ||||
|         CHECK_RESULT(dfu_file_process_targets(&page_task, update_task->file, valid_targets)); | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| static bool update_task_write_stack_data(UpdateTask* update_task) { | ||||
|     furi_check(storage_file_is_open(update_task->file)); | ||||
|     const size_t FLASH_PAGE_SIZE = furi_hal_flash_get_page_size(); | ||||
| 
 | ||||
|     uint32_t stack_size = storage_file_size(update_task->file); | ||||
|     storage_file_seek(update_task->file, 0, true); | ||||
| 
 | ||||
|     if(!check_address_boundaries(update_task->manifest->radio_address) || | ||||
|        !check_address_boundaries(update_task->manifest->radio_address + stack_size)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     update_task_set_progress(update_task, UpdateTaskStageRadioWrite, 0); | ||||
|     uint8_t* fw_block = malloc(FLASH_PAGE_SIZE); | ||||
|     uint16_t bytes_read = 0; | ||||
|     uint32_t element_offs = 0; | ||||
| 
 | ||||
|     while(element_offs < stack_size) { | ||||
|         uint32_t n_bytes_to_read = FLASH_PAGE_SIZE; | ||||
|         if((element_offs + n_bytes_to_read) > stack_size) { | ||||
|             n_bytes_to_read = stack_size - element_offs; | ||||
|         } | ||||
| 
 | ||||
|         bytes_read = storage_file_read(update_task->file, fw_block, n_bytes_to_read); | ||||
|         if(bytes_read == 0) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         int16_t i_page = | ||||
|             furi_hal_flash_get_page_number(update_task->manifest->radio_address + element_offs); | ||||
|         if(i_page < 0) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(!furi_hal_flash_program_page(i_page, fw_block, bytes_read)) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         element_offs += bytes_read; | ||||
|         update_task_set_progress( | ||||
|             update_task, UpdateTaskStageProgress, element_offs * 100 / stack_size); | ||||
|     } | ||||
| 
 | ||||
|     free(fw_block); | ||||
|     return element_offs == stack_size; | ||||
| } | ||||
| 
 | ||||
| static void update_task_wait_for_restart(UpdateTask* update_task) { | ||||
|     update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10); | ||||
|     osDelay(C2_MODE_SWITCH_TIMEOUT); | ||||
|     furi_crash("C2 timeout"); | ||||
| } | ||||
| 
 | ||||
| static bool update_task_write_stack(UpdateTask* update_task) { | ||||
|     bool success = false; | ||||
|     do { | ||||
|         FURI_LOG_W(TAG, "Writing stack"); | ||||
|         update_task_set_progress(update_task, UpdateTaskStageRadioImageValidate, 0); | ||||
|         CHECK_RESULT(update_task_open_file(update_task, update_task->manifest->radio_image)); | ||||
|         CHECK_RESULT( | ||||
|             crc32_calc_file(update_task->file, &update_task_file_progress, update_task) == | ||||
|             update_task->manifest->radio_crc); | ||||
| 
 | ||||
|         CHECK_RESULT(update_task_write_stack_data(update_task)); | ||||
|         update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 0); | ||||
|         CHECK_RESULT( | ||||
|             ble_glue_fus_stack_install(update_task->manifest->radio_address, 0) != | ||||
|             BleGlueCommandResultError); | ||||
|         update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 80); | ||||
|         CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK); | ||||
|         update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 100); | ||||
|         /* ...system will restart here. */ | ||||
|         update_task_wait_for_restart(update_task); | ||||
|         success = true; | ||||
|     } while(false); | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| static bool update_task_remove_stack(UpdateTask* update_task) { | ||||
|     bool success = false; | ||||
|     do { | ||||
|         FURI_LOG_W(TAG, "Removing stack"); | ||||
|         update_task_set_progress(update_task, UpdateTaskStageRadioErase, 30); | ||||
|         CHECK_RESULT(ble_glue_fus_stack_delete() != BleGlueCommandResultError); | ||||
|         update_task_set_progress(update_task, UpdateTaskStageRadioErase, 80); | ||||
|         CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK); | ||||
|         update_task_set_progress(update_task, UpdateTaskStageRadioErase, 100); | ||||
|         /* ...system will restart here. */ | ||||
|         update_task_wait_for_restart(update_task); | ||||
|         success = true; | ||||
|     } while(false); | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| static bool update_task_manage_radiostack(UpdateTask* update_task) { | ||||
|     bool success = false; | ||||
|     do { | ||||
|         CHECK_RESULT(ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)); | ||||
| 
 | ||||
|         const BleGlueC2Info* c2_state = ble_glue_get_c2_info(); | ||||
| 
 | ||||
|         const UpdateManifestRadioVersion* radio_ver = &update_task->manifest->radio_version; | ||||
|         bool stack_version_match = (c2_state->VersionMajor == radio_ver->version.major) && | ||||
|                                    (c2_state->VersionMinor == radio_ver->version.minor) && | ||||
|                                    (c2_state->VersionSub == radio_ver->version.sub) && | ||||
|                                    (c2_state->VersionBranch == radio_ver->version.branch) && | ||||
|                                    (c2_state->VersionReleaseType == radio_ver->version.release); | ||||
|         bool stack_missing = (c2_state->VersionMajor == 0) && (c2_state->VersionMinor == 0); | ||||
| 
 | ||||
|         if(c2_state->mode == BleGlueC2ModeStack) { | ||||
|             /* Stack type is not available when we have FUS running. */ | ||||
|             bool total_stack_match = stack_version_match && | ||||
|                                      (c2_state->StackType == radio_ver->version.type); | ||||
|             if(total_stack_match) { | ||||
|                 /* Nothing to do. */ | ||||
|                 FURI_LOG_W(TAG, "Stack version is up2date"); | ||||
|                 furi_hal_rtc_reset_flag(FuriHalRtcFlagC2Update); | ||||
|                 success = true; | ||||
|                 break; | ||||
|             } else { | ||||
|                 /* Version or type mismatch. Let's boot to FUS and start updating. */ | ||||
|                 FURI_LOG_W(TAG, "Restarting to FUS"); | ||||
|                 furi_hal_rtc_set_flag(FuriHalRtcFlagC2Update); | ||||
|                 CHECK_RESULT(furi_hal_bt_ensure_c2_mode(BleGlueC2ModeFUS)); | ||||
|                 /* ...system will restart here. */ | ||||
|                 update_task_wait_for_restart(update_task); | ||||
|             } | ||||
|         } else if(c2_state->mode == BleGlueC2ModeFUS) { | ||||
|             /* OK, we're in FUS mode. */ | ||||
|             update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10); | ||||
|             FURI_LOG_W(TAG, "Waiting for FUS to settle"); | ||||
|             ble_glue_fus_wait_operation(); | ||||
|             if(stack_version_match) { | ||||
|                 /* We can't check StackType with FUS, but partial version matches */ | ||||
|                 if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagC2Update)) { | ||||
|                     /* This flag was set when full version was checked.
 | ||||
|                      * And something in versions of the stack didn't match. | ||||
|                      * So, clear the flag and drop the stack. */ | ||||
|                     furi_hal_rtc_reset_flag(FuriHalRtcFlagC2Update); | ||||
|                     FURI_LOG_W(TAG, "Forcing stack removal (match)"); | ||||
|                     CHECK_RESULT(update_task_remove_stack(update_task)); | ||||
|                 } else { | ||||
|                     /* We might just had the stack installed.
 | ||||
|                      * Let's start it up to check its version */ | ||||
|                     FURI_LOG_W(TAG, "Starting stack to check full version"); | ||||
|                     update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 40); | ||||
|                     CHECK_RESULT(furi_hal_bt_ensure_c2_mode(BleGlueC2ModeStack)); | ||||
|                     /* ...system will restart here. */ | ||||
|                     update_task_wait_for_restart(update_task); | ||||
|                 } | ||||
|             } else { | ||||
|                 if(stack_missing) { | ||||
|                     /* Install stack. */ | ||||
|                     CHECK_RESULT(update_task_write_stack(update_task)); | ||||
|                 } else { | ||||
|                     CHECK_RESULT(update_task_remove_stack(update_task)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } while(false); | ||||
| 
 | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| bool update_task_validate_optionbytes(UpdateTask* update_task) { | ||||
|     update_task_set_progress(update_task, UpdateTaskStageOBValidation, 0); | ||||
| 
 | ||||
|     bool match = true; | ||||
|     bool ob_dirty = false; | ||||
|     const UpdateManifest* manifest = update_task->manifest; | ||||
|     const FuriHalFlashRawOptionByteData* device_data = furi_hal_flash_ob_get_raw_ptr(); | ||||
|     for(size_t idx = 0; idx < FURI_HAL_FLASH_OB_TOTAL_VALUES; ++idx) { | ||||
|         update_task_set_progress( | ||||
|             update_task, UpdateTaskStageProgress, idx * 100 / FURI_HAL_FLASH_OB_TOTAL_VALUES); | ||||
|         const uint32_t ref_value = manifest->ob_reference.obs[idx].values.base; | ||||
|         const uint32_t device_ob_value = device_data->obs[idx].values.base; | ||||
|         const uint32_t device_ob_value_masked = device_ob_value & | ||||
|                                                 manifest->ob_compare_mask.obs[idx].values.base; | ||||
|         if(ref_value != device_ob_value_masked) { | ||||
|             match = false; | ||||
|             FURI_LOG_E( | ||||
|                 TAG, | ||||
|                 "OB MISMATCH: #%d: real %08X != %08X (exp.), full %08X", | ||||
|                 idx, | ||||
|                 device_ob_value_masked, | ||||
|                 ref_value, | ||||
|                 device_ob_value); | ||||
| 
 | ||||
|             /* any bits we are allowed to write?.. */ | ||||
|             bool can_patch = ((device_ob_value_masked ^ ref_value) & | ||||
|                               manifest->ob_write_mask.obs[idx].values.base) != 0; | ||||
| 
 | ||||
|             if(can_patch) { | ||||
|                 /* patch & restart loop */ | ||||
|                 const uint32_t patched_value = | ||||
|                     /* take all non-writable bits from real value */ | ||||
|                     (device_ob_value & ~(manifest->ob_write_mask.obs[idx].values.base)) | | ||||
|                     /* take all writable bits from reference value */ | ||||
|                     (manifest->ob_reference.obs[idx].values.base & | ||||
|                      manifest->ob_write_mask.obs[idx].values.base); | ||||
| 
 | ||||
|                 FURI_LOG_W(TAG, "Fixing up OB byte #%d to %08X", idx, patched_value); | ||||
|                 ob_dirty = true; | ||||
| 
 | ||||
|                 bool is_fixed = furi_hal_flash_ob_set_word(idx, patched_value) && | ||||
|                                 ((device_data->obs[idx].values.base & | ||||
|                                   manifest->ob_compare_mask.obs[idx].values.base) == ref_value); | ||||
| 
 | ||||
|                 if(!is_fixed) { | ||||
|                     /* Things are so bad that fixing what we are allowed to still doesn't match
 | ||||
|                      * reference value  | ||||
|                      */ | ||||
|                     FURI_LOG_W( | ||||
|                         TAG, | ||||
|                         "OB #%d is FUBAR (fixed&masked %08X, not %08X)", | ||||
|                         idx, | ||||
|                         patched_value, | ||||
|                         ref_value); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             FURI_LOG_I( | ||||
|                 TAG, | ||||
|                 "OB MATCH: #%d: real %08X == %08X (exp.)", | ||||
|                 idx, | ||||
|                 device_ob_value_masked, | ||||
|                 ref_value); | ||||
|         } | ||||
|     } | ||||
|     if(!match) { | ||||
|         update_task_set_progress(update_task, UpdateTaskStageOBError, 95); | ||||
|     } | ||||
| 
 | ||||
|     if(ob_dirty) { | ||||
|         FURI_LOG_W(TAG, "OB were changed, applying"); | ||||
|         furi_hal_flash_ob_apply(); | ||||
|     } | ||||
|     return match; | ||||
| } | ||||
| 
 | ||||
| int32_t update_task_worker_flash_writer(void* context) { | ||||
|     furi_assert(context); | ||||
|     UpdateTask* update_task = context; | ||||
|     bool success = false; | ||||
| 
 | ||||
|     update_task->state.current_stage_idx = 0; | ||||
|     update_task->state.total_stages = 0; | ||||
| 
 | ||||
|     do { | ||||
|         CHECK_RESULT(update_task_parse_manifest(update_task)); | ||||
| 
 | ||||
|         if(!string_empty_p(update_task->manifest->radio_image)) { | ||||
|             CHECK_RESULT(update_task_manage_radiostack(update_task)); | ||||
|         } | ||||
| 
 | ||||
|         bool check_ob = update_manifest_has_obdata(update_task->manifest); | ||||
|         if(check_ob) { | ||||
|             update_task->state.total_stages++; | ||||
|             CHECK_RESULT(update_task_validate_optionbytes(update_task)); | ||||
|         } | ||||
| 
 | ||||
|         if(!string_empty_p(update_task->manifest->firmware_dfu_image)) { | ||||
|             update_task->state.total_stages += 4; | ||||
|             CHECK_RESULT(update_task_write_dfu(update_task)); | ||||
|         } | ||||
| 
 | ||||
|         furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); | ||||
| 
 | ||||
|         update_task_set_progress(update_task, UpdateTaskStageCompleted, 100); | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return success ? UPDATE_TASK_NOERR : UPDATE_TASK_FAILED; | ||||
| } | ||||
							
								
								
									
										1
									
								
								assets/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								assets/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1,2 @@ | ||||
| /headers | ||||
| /core2_firmware | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| PROJECT_ROOT		= $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))..) | ||||
| 
 | ||||
| include				$(PROJECT_ROOT)/assets/assets.mk | ||||
| include				$(PROJECT_ROOT)/assets/copro.mk | ||||
| 
 | ||||
| .PHONY: all | ||||
| all: icons protobuf dolphin manifest | ||||
| @ -35,7 +36,13 @@ manifest: | ||||
| .PHONY: dolphin | ||||
| dolphin: $(DOLPHIN_EXTERNAL_OUTPUT_DIR) | ||||
| 
 | ||||
| .PHONY: copro_bundle | ||||
| copro_bundle: | ||||
| 	@mkdir -p $(COPRO_BUNDLE_DIR) | ||||
| 	@$(ASSETS_COMPILER) copro $(COPRO_CUBE_DIR) $(COPRO_BUNDLE_DIR) $(COPRO_MCU_FAMILY) --cube_ver=$(COPRO_CUBE_VERSION) --stack_type=$(COPRO_STACK_TYPE) --stack_file=$(COPRO_STACK_BIN) --stack_addr=$(COPRO_STACK_ADDR) | ||||
| 
 | ||||
| clean: | ||||
| 	@echo "\tCLEAN\t" | ||||
| 	@$(RM) $(ASSETS_COMPILED_DIR)/* | ||||
| 	@$(RM) -rf $(COPRO_BUNDLE_DIR) | ||||
| 	@$(RM) -rf $(DOLPHIN_EXTERNAL_OUTPUT_DIR) | ||||
|  | ||||
| @ -10,6 +10,13 @@ | ||||
| make all | ||||
| ``` | ||||
| 
 | ||||
| # Compiling with Docker-Compose | ||||
| 
 | ||||
| ```bash | ||||
| docker-compose exec dev make -C assets clean | ||||
| docker-compose exec dev make -C assets all | ||||
| ``` | ||||
| 
 | ||||
| # Asset naming rules | ||||
| 
 | ||||
| ## Images and Animations | ||||
|  | ||||
							
								
								
									
										12
									
								
								assets/copro.mk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								assets/copro.mk
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| COPRO_CUBE_VERSION	:= 1.13.3 | ||||
| COPRO_MCU_FAMILY	:= STM32WB5x | ||||
| COPRO_STACK_BIN		:= stm32wb5x_BLE_Stack_light_fw.bin | ||||
| #  See __STACK_TYPE_CODES in scripts/flipper/assets/coprobin.py
 | ||||
| COPRO_STACK_TYPE	:= ble_light | ||||
| #  Keep 0 for auto, or put a value from release_notes for chosen stack
 | ||||
| COPRO_STACK_ADDR	:= 0 | ||||
| 
 | ||||
| COPRO_BUNDLE_DIR	:= $(ASSETS_DIR)/core2_firmware | ||||
| COPRO_CUBE_DIR		:= $(PROJECT_ROOT)/lib/STM32CubeWB | ||||
| COPRO_FIRMWARE_DIR	:= $(COPRO_CUBE_DIR)/Projects/STM32WB_Copro_Wireless_Binaries/$(COPRO_MCU_FAMILY) | ||||
| COPRO_STACK_BIN_PATH	:= $(COPRO_FIRMWARE_DIR)/$(COPRO_STACK_BIN) | ||||
| @ -1,5 +1,5 @@ | ||||
| V:0 | ||||
| T:1650389893 | ||||
| T:1651076680 | ||||
| D:badusb | ||||
| D:dolphin | ||||
| D:infrared | ||||
| @ -222,7 +222,7 @@ F:33b8fde22f34ef556b64b77164bc19b0:578:dolphin/L3_Lab_research_128x54/frame_8.bm | ||||
| F:f267f0654781049ca323b11bb4375519:581:dolphin/L3_Lab_research_128x54/frame_9.bm | ||||
| F:41106c0cbc5144f151b2b2d3daaa0527:727:dolphin/L3_Lab_research_128x54/meta.txt | ||||
| D:infrared/assets | ||||
| F:5b16e1a59daf3ef1d0fc95b3b5596d67:74300:infrared/assets/tv.ir | ||||
| F:d895fda2f48c6cc4c55e8a398ff52e43:74300:infrared/assets/tv.ir | ||||
| D:nfc/assets | ||||
| F:c6826a621d081d68309e4be424d3d974:4715:nfc/assets/aid.nfc | ||||
| F:86efbebdf41bb6bf15cc51ef88f069d5:2565:nfc/assets/country_code.nfc | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| Filetype: IR signals file | ||||
| Filetype: IR library file | ||||
| Version: 1 | ||||
| #  | ||||
| name: POWER | ||||
|  | ||||
| @ -7,7 +7,8 @@ | ||||
| #include <flipper_format/flipper_format.h> | ||||
| 
 | ||||
| #include <update_util/update_manifest.h> | ||||
| #include <lib/toolbox/path.h> | ||||
| #include <toolbox/path.h> | ||||
| #include <toolbox/crc32_calc.h> | ||||
| 
 | ||||
| static FATFS* pfs = NULL; | ||||
| 
 | ||||
| @ -27,7 +28,6 @@ static bool flipper_update_init() { | ||||
|     furi_hal_delay_init(); | ||||
| 
 | ||||
|     furi_hal_spi_init(); | ||||
|     furi_hal_crc_init(false); | ||||
| 
 | ||||
|     MX_FATFS_Init(); | ||||
|     if(!hal_sd_detect()) { | ||||
| @ -62,17 +62,15 @@ static bool flipper_update_load_stage(const string_t work_dir, UpdateManifest* m | ||||
|     uint32_t bytes_read = 0; | ||||
|     const uint16_t MAX_READ = 0xFFFF; | ||||
| 
 | ||||
|     furi_hal_crc_reset(); | ||||
|     uint32_t crc = 0; | ||||
|     do { | ||||
|         uint16_t size_read = 0; | ||||
|         if(f_read(&file, img + bytes_read, MAX_READ, &size_read) != FR_OK) { | ||||
|             break; | ||||
|         } | ||||
|         crc = furi_hal_crc_feed(img + bytes_read, size_read); | ||||
|         crc = crc32_calc_buffer(crc, img + bytes_read, size_read); | ||||
|         bytes_read += size_read; | ||||
|     } while(bytes_read == MAX_READ); | ||||
|     furi_hal_crc_reset(); | ||||
| 
 | ||||
|     do { | ||||
|         if((bytes_read != stat.fsize) || (crc != manifest->staged_loader_crc)) { | ||||
|  | ||||
| @ -396,7 +396,7 @@ typedef enum { | ||||
|  * keep debugger enabled while in any low power mode when set to 1 | ||||
|  * should be set to 0 in production | ||||
|  */ | ||||
| #define CFG_DEBUGGER_SUPPORTED 0 | ||||
| #define CFG_DEBUGGER_SUPPORTED 1 | ||||
| 
 | ||||
| /**
 | ||||
|  * When set to 1, the traces are enabled in the BLE services | ||||
|  | ||||
| @ -1,25 +1,3 @@ | ||||
| /* USER CODE BEGIN Header */ | ||||
| /**
 | ||||
|  ****************************************************************************** | ||||
|   * File Name          : app_debug.c | ||||
|   * Description        : Debug capabilities source file for STM32WPAN Middleware | ||||
|  ****************************************************************************** | ||||
|   * @attention | ||||
|   * | ||||
|   * <h2><center>© Copyright (c) 2020 STMicroelectronics. | ||||
|   * All rights reserved.</center></h2> | ||||
|   * | ||||
|   * This software component is licensed by ST under Ultimate Liberty license | ||||
|   * SLA0044, the "License"; You may not use this file except in compliance with | ||||
|   * the License. You may obtain a copy of the License at: | ||||
|   *                             www.st.com/SLA0044 | ||||
|   * | ||||
|  ****************************************************************************** | ||||
|  */ | ||||
| /* USER CODE END Header */ | ||||
| 
 | ||||
| /* Includes ------------------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN Includes */ | ||||
| #include "utilities_common.h" | ||||
| 
 | ||||
| #include "app_common.h" | ||||
| @ -28,10 +6,7 @@ | ||||
| #include "tl.h" | ||||
| #include "dbg_trace.h" | ||||
| #include <furi_hal.h> | ||||
| /* USER CODE END Includes */ | ||||
| 
 | ||||
| /* Private typedef -----------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN PTD */ | ||||
| typedef PACKED_STRUCT { | ||||
|     GPIO_TypeDef* port; | ||||
|     uint16_t pin; | ||||
| @ -39,10 +14,7 @@ typedef PACKED_STRUCT { | ||||
|     uint8_t reserved; | ||||
| } | ||||
| APPD_GpioConfig_t; | ||||
| /* USER CODE END PTD */ | ||||
| 
 | ||||
| /* Private defines -----------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN PD */ | ||||
| #define GPIO_NBR_OF_RF_SIGNALS 9 | ||||
| #define GPIO_CFG_NBR_OF_FEATURES 34 | ||||
| #define NBR_OF_TRACES_CONFIG_PARAMETERS 4 | ||||
| @ -51,12 +23,11 @@ APPD_GpioConfig_t; | ||||
| /**
 | ||||
|  * THIS SHALL BE SET TO A VALUE DIFFERENT FROM 0 ONLY ON REQUEST FROM ST SUPPORT | ||||
|  */ | ||||
| #define BLE_DTB_CFG 7 | ||||
| #define BLE_DTB_CFG 0 | ||||
| // #define BLE_DTB_CFG 7
 | ||||
| #define SYS_DBG_CFG1 (SHCI_C2_DEBUG_OPTIONS_IPCORE_LP | SHCI_C2_DEBUG_OPTIONS_CPU2_STOP_EN) | ||||
| /* USER CODE END PD */ | ||||
| 
 | ||||
| /* Private variables ---------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN PV */ | ||||
| PLACE_IN_SECTION("MB_MEM2") | ||||
| ALIGN(4) static SHCI_C2_DEBUG_TracesConfig_t APPD_TracesConfig = {0, 0, 0, 0}; | ||||
| PLACE_IN_SECTION("MB_MEM2") | ||||
| @ -91,7 +62,7 @@ static const APPD_GpioConfig_t aGpioConfigList[GPIO_CFG_NBR_OF_FEATURES] = { | ||||
|     {GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_TRACES_TX - Set on Entry / Reset on Exit */ | ||||
|     {GPIOA, LL_GPIO_PIN_6, 1, 0}, /* HARD_FAULT - Set on Entry / Reset on Exit */ | ||||
|     /* From v1.1.1 */ | ||||
|     {GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IP_CORE_LP_STATUS - Set on Entry / Reset on Exit */ | ||||
|     {GPIOC, LL_GPIO_PIN_1, 1, 0}, /* IP_CORE_LP_STATUS - Set on Entry / Reset on Exit */ | ||||
|     /* From v1.2.0 */ | ||||
|     {GPIOA, LL_GPIO_PIN_0, 0, 0}, /* END_OF_CONNECTION_EVENT - Set on Entry / Reset on Exit */ | ||||
|     {GPIOA, LL_GPIO_PIN_0, 0, 0}, /* TIMER_SERVER_CALLBACK - Toggle on Entry */ | ||||
| @ -130,65 +101,20 @@ static const APPD_GpioConfig_t aRfConfigList[GPIO_NBR_OF_RF_SIGNALS] = { | ||||
|     {GPIOB, LL_GPIO_PIN_10, 0, 0}, /* DTB18 - FSM4 */ | ||||
| }; | ||||
| #endif | ||||
| /* USER CODE END PV */ | ||||
| 
 | ||||
| /* Global variables ----------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN GV */ | ||||
| /* USER CODE END GV */ | ||||
| 
 | ||||
| /* Private function prototypes -----------------------------------------------*/ | ||||
| /* USER CODE BEGIN PFP */ | ||||
| static void APPD_SetCPU2GpioConfig(void); | ||||
| static void APPD_BleDtbCfg(void); | ||||
| /* USER CODE END PFP */ | ||||
| 
 | ||||
| /* Functions Definition ------------------------------------------------------*/ | ||||
| void APPD_Init(void) { | ||||
| /* USER CODE BEGIN APPD_Init */ | ||||
| #if(CFG_DEBUGGER_SUPPORTED == 1) | ||||
|     /**
 | ||||
|    * Keep debugger enabled while in any low power mode | ||||
|    */ | ||||
|     LL_DBGMCU_EnableDBGSleepMode(); | ||||
|     LL_DBGMCU_EnableDBGStopMode(); | ||||
| 
 | ||||
|     /***************** ENABLE DEBUGGER *************************************/ | ||||
|     LL_EXTI_EnableIT_32_63(LL_EXTI_LINE_48); | ||||
| 
 | ||||
| #else | ||||
|     LL_GPIO_InitTypeDef gpio_config = {0}; | ||||
|     LL_PWR_EnableVddUSB(); | ||||
| 
 | ||||
|     gpio_config.Mode = LL_GPIO_MODE_ANALOG; | ||||
|     gpio_config.Speed = LL_GPIO_SPEED_FREQ_LOW; | ||||
|     // gpio_config.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
 | ||||
|     // gpio_config.Pull = LL_GPIO_PULL_NO;
 | ||||
|     // gpio_config.Alternate = LL_GPIO_AF_10;
 | ||||
|     gpio_config.Pin = LL_GPIO_PIN_15 | LL_GPIO_PIN_14 | LL_GPIO_PIN_13; | ||||
|     LL_GPIO_Init(GPIOA, &gpio_config); | ||||
| 
 | ||||
|     gpio_config.Pin = LL_GPIO_PIN_4 | LL_GPIO_PIN_3; | ||||
|     LL_GPIO_Init(GPIOB, &gpio_config); | ||||
| 
 | ||||
|     LL_DBGMCU_DisableDBGSleepMode(); | ||||
|     LL_DBGMCU_DisableDBGStopMode(); | ||||
|     LL_DBGMCU_DisableDBGStandbyMode(); | ||||
| 
 | ||||
| #endif /* (CFG_DEBUGGER_SUPPORTED == 1) */ | ||||
| 
 | ||||
| void APPD_Init() { | ||||
| #if(CFG_DEBUG_TRACE != 0) | ||||
|     DbgTraceInit(); | ||||
| #endif | ||||
| 
 | ||||
|     APPD_SetCPU2GpioConfig(); | ||||
|     APPD_BleDtbCfg(); | ||||
| 
 | ||||
|     /* USER CODE END APPD_Init */ | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
| void APPD_EnableCPU2(void) { | ||||
|     /* USER CODE BEGIN APPD_EnableCPU2 */ | ||||
|     SHCI_C2_DEBUG_Init_Cmd_Packet_t DebugCmdPacket = { | ||||
|         {{0, 0, 0}}, /**< Does not need to be initialized */ | ||||
|         {(uint8_t*)aGpioConfigList, | ||||
| @ -204,6 +130,7 @@ void APPD_EnableCPU2(void) { | ||||
|     /** GPIO DEBUG Initialization */ | ||||
|     SHCI_C2_DEBUG_Init(&DebugCmdPacket); | ||||
| 
 | ||||
|     // We don't need External Power Amplifier
 | ||||
|     // LL_GPIO_InitTypeDef  gpio_config;
 | ||||
|     // gpio_config.Pull = GPIO_NOPULL;
 | ||||
|     // gpio_config.Mode = GPIO_MODE_OUTPUT_PP;
 | ||||
| @ -212,17 +139,10 @@ void APPD_EnableCPU2(void) { | ||||
|     // HAL_GPIO_Init(GPIOC, &gpio_config);
 | ||||
|     // SHCI_C2_ExtpaConfig((uint32_t)GPIOC, LL_GPIO_PIN_3, EXT_PA_ENABLED_LOW, EXT_PA_ENABLED);
 | ||||
| 
 | ||||
|     /* USER CODE END APPD_EnableCPU2 */ | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
| /*************************************************************
 | ||||
|  * | ||||
|  * LOCAL FUNCTIONS | ||||
|  * | ||||
|  *************************************************************/ | ||||
| static void APPD_SetCPU2GpioConfig(void) { | ||||
|     /* USER CODE BEGIN APPD_SetCPU2GpioConfig */ | ||||
|     LL_GPIO_InitTypeDef gpio_config = {0}; | ||||
|     uint8_t local_loop; | ||||
|     uint16_t gpioa_pin_list; | ||||
| @ -259,8 +179,9 @@ static void APPD_SetCPU2GpioConfig(void) { | ||||
|     gpio_config.OutputType = LL_GPIO_OUTPUT_PUSHPULL; | ||||
|     gpio_config.Pull = LL_GPIO_PULL_NO; | ||||
| 
 | ||||
|     gpio_config.Pin = LL_GPIO_PIN_15 | LL_GPIO_PIN_14 | LL_GPIO_PIN_13; | ||||
|     LL_GPIO_Init(GPIOA, &gpio_config); | ||||
|     // Never disable SWD, why would you?
 | ||||
|     // gpio_config.Pin = LL_GPIO_PIN_15 | LL_GPIO_PIN_14 | LL_GPIO_PIN_13;
 | ||||
|     // LL_GPIO_Init(GPIOA, &gpio_config);
 | ||||
| 
 | ||||
|     if(gpioa_pin_list != 0) { | ||||
|         gpio_config.Pin = gpioa_pin_list; | ||||
| @ -282,13 +203,9 @@ static void APPD_SetCPU2GpioConfig(void) { | ||||
|         LL_GPIO_Init(GPIOC, &gpio_config); | ||||
|         LL_GPIO_ResetOutputPin(GPIOC, gpioa_pin_list); | ||||
|     } | ||||
| 
 | ||||
|     /* USER CODE END APPD_SetCPU2GpioConfig */ | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
| static void APPD_BleDtbCfg(void) { | ||||
| /* USER CODE BEGIN APPD_BleDtbCfg */ | ||||
| #if(BLE_DTB_CFG != 0) | ||||
|     LL_GPIO_InitTypeDef gpio_config = {0}; | ||||
|     uint8_t local_loop; | ||||
| @ -304,11 +221,9 @@ static void APPD_BleDtbCfg(void) { | ||||
|             case(uint32_t)GPIOA: | ||||
|                 gpioa_pin_list |= aRfConfigList[local_loop].pin; | ||||
|                 break; | ||||
| 
 | ||||
|             case(uint32_t)GPIOB: | ||||
|                 gpiob_pin_list |= aRfConfigList[local_loop].pin; | ||||
|                 break; | ||||
| 
 | ||||
|             default: | ||||
|                 break; | ||||
|             } | ||||
| @ -334,16 +249,8 @@ static void APPD_BleDtbCfg(void) { | ||||
|         LL_GPIO_Init(GPIOB, &gpio_config); | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     /* USER CODE END APPD_BleDtbCfg */ | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
| /*************************************************************
 | ||||
|  * | ||||
|  * WRAP FUNCTIONS | ||||
|  * | ||||
| *************************************************************/ | ||||
| #if(CFG_DEBUG_TRACE != 0) | ||||
| void DbgOutputInit(void) { | ||||
| } | ||||
| @ -353,5 +260,3 @@ void DbgOutputTraces(uint8_t* p_data, uint16_t size, void (*cb)(void)) { | ||||
|     cb(); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ | ||||
|  | ||||
| @ -6,7 +6,9 @@ | ||||
| #include "shci.h" | ||||
| #include "shci_tl.h" | ||||
| #include "app_debug.h" | ||||
| 
 | ||||
| #include <furi_hal.h> | ||||
| #include <shci/shci.h> | ||||
| 
 | ||||
| #define TAG "Core2" | ||||
| 
 | ||||
| @ -27,22 +29,13 @@ PLACE_IN_SECTION("MB_MEM2") | ||||
| ALIGN(4) | ||||
| static uint8_t ble_glue_ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; | ||||
| 
 | ||||
| typedef enum { | ||||
|     // Stage 1: core2 startup and FUS
 | ||||
|     BleGlueStatusStartup, | ||||
|     BleGlueStatusBroken, | ||||
|     BleGlueStatusFusStarted, | ||||
|     // Stage 2: radio stack
 | ||||
|     BleGlueStatusRadioStackStarted, | ||||
|     BleGlueStatusRadioStackMissing | ||||
| } BleGlueStatus; | ||||
| 
 | ||||
| typedef struct { | ||||
|     osMutexId_t shci_mtx; | ||||
|     osSemaphoreId_t shci_sem; | ||||
|     FuriThread* thread; | ||||
|     BleGlueStatus status; | ||||
|     BleGlueKeyStorageChangedCallback callback; | ||||
|     BleGlueC2Info c2_info; | ||||
|     void* context; | ||||
| } BleGlue; | ||||
| 
 | ||||
| @ -70,9 +63,10 @@ void ble_glue_init() { | ||||
|     LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_HSI); | ||||
|     /* Initialize the CPU2 reset value before starting CPU2 with C2BOOT */ | ||||
|     LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); | ||||
|     furi_hal_power_insomnia_enter(); | ||||
| 
 | ||||
|     // APPD_Init();
 | ||||
| #ifdef BLE_GLUE_DEBUG | ||||
|     APPD_Init(); | ||||
| #endif | ||||
| 
 | ||||
|     // Initialize all transport layers
 | ||||
|     TL_MM_Config_t tl_mm_config; | ||||
| @ -111,41 +105,103 @@ void ble_glue_init() { | ||||
|      */ | ||||
| } | ||||
| 
 | ||||
| bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info) { | ||||
|     bool ret = false; | ||||
| const BleGlueC2Info* ble_glue_get_c2_info() { | ||||
|     return &ble_glue->c2_info; | ||||
| } | ||||
| 
 | ||||
|     size_t countdown = 1000; | ||||
|     while(countdown > 0) { | ||||
|         if(ble_glue->status == BleGlueStatusFusStarted) { | ||||
|             ret = true; | ||||
|             break; | ||||
| BleGlueStatus ble_glue_get_c2_status() { | ||||
|     return ble_glue->status; | ||||
| } | ||||
| 
 | ||||
| static void ble_glue_update_c2_fw_info() { | ||||
|     WirelessFwInfo_t wireless_info; | ||||
|     SHCI_GetWirelessFwInfo(&wireless_info); | ||||
|     BleGlueC2Info* local_info = &ble_glue->c2_info; | ||||
| 
 | ||||
|     local_info->VersionMajor = wireless_info.VersionMajor; | ||||
|     local_info->VersionMinor = wireless_info.VersionMinor; | ||||
|     local_info->VersionMajor = wireless_info.VersionMajor; | ||||
|     local_info->VersionMinor = wireless_info.VersionMinor; | ||||
|     local_info->VersionSub = wireless_info.VersionSub; | ||||
|     local_info->VersionBranch = wireless_info.VersionBranch; | ||||
|     local_info->VersionReleaseType = wireless_info.VersionReleaseType; | ||||
| 
 | ||||
|     local_info->MemorySizeSram2B = wireless_info.MemorySizeSram2B; | ||||
|     local_info->MemorySizeSram2A = wireless_info.MemorySizeSram2A; | ||||
|     local_info->MemorySizeSram1 = wireless_info.MemorySizeSram1; | ||||
|     local_info->MemorySizeFlash = wireless_info.MemorySizeFlash; | ||||
| 
 | ||||
|     local_info->StackType = wireless_info.StackType; | ||||
| 
 | ||||
|     local_info->FusVersionMajor = wireless_info.FusVersionMajor; | ||||
|     local_info->FusVersionMinor = wireless_info.FusVersionMinor; | ||||
|     local_info->FusVersionSub = wireless_info.FusVersionSub; | ||||
|     local_info->FusMemorySizeSram2B = wireless_info.FusMemorySizeSram2B; | ||||
|     local_info->FusMemorySizeSram2A = wireless_info.FusMemorySizeSram2A; | ||||
|     local_info->FusMemorySizeFlash = wireless_info.FusMemorySizeFlash; | ||||
| } | ||||
| 
 | ||||
| static void ble_glue_dump_stack_info() { | ||||
|     const BleGlueC2Info* c2_info = &ble_glue->c2_info; | ||||
|     FURI_LOG_I( | ||||
|         TAG, | ||||
|         "Core2: FUS: %d.%d.%d, mem %d/%d, flash %d pages", | ||||
|         c2_info->FusVersionMajor, | ||||
|         c2_info->FusVersionMinor, | ||||
|         c2_info->FusVersionSub, | ||||
|         c2_info->FusMemorySizeSram2B, | ||||
|         c2_info->FusMemorySizeSram2A, | ||||
|         c2_info->FusMemorySizeFlash); | ||||
|     FURI_LOG_I( | ||||
|         TAG, | ||||
|         "Core2: Stack: %d.%d.%d, branch %d, reltype %d, stacktype %d, flash %d pages", | ||||
|         c2_info->VersionMajor, | ||||
|         c2_info->VersionMinor, | ||||
|         c2_info->VersionSub, | ||||
|         c2_info->VersionBranch, | ||||
|         c2_info->VersionReleaseType, | ||||
|         c2_info->StackType, | ||||
|         c2_info->MemorySizeFlash); | ||||
| } | ||||
| 
 | ||||
| bool ble_glue_wait_for_c2_start(int32_t timeout) { | ||||
|     bool started = false; | ||||
| 
 | ||||
|     do { | ||||
|         // TODO: use mutex?
 | ||||
|         started = ble_glue->status == BleGlueStatusC2Started; | ||||
|         if(!started) { | ||||
|             timeout--; | ||||
|             osDelay(1); | ||||
|         } | ||||
|         countdown--; | ||||
|         osDelay(1); | ||||
|     } | ||||
|     } while(!started && (timeout > 0)); | ||||
| 
 | ||||
|     if(ble_glue->status == BleGlueStatusFusStarted) { | ||||
|         SHCI_GetWirelessFwInfo(info); | ||||
|     if(started) { | ||||
|         FURI_LOG_I( | ||||
|             TAG, | ||||
|             "C2 boot completed, mode: %s", | ||||
|             ble_glue->c2_info.mode == BleGlueC2ModeFUS ? "FUS" : "Stack"); | ||||
|         ble_glue_update_c2_fw_info(); | ||||
|         ble_glue_dump_stack_info(); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "Failed to start FUS"); | ||||
|         FURI_LOG_E(TAG, "C2 startup failed"); | ||||
|         ble_glue->status = BleGlueStatusBroken; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
|     return started; | ||||
| } | ||||
| 
 | ||||
| bool ble_glue_start() { | ||||
|     furi_assert(ble_glue); | ||||
| 
 | ||||
|     if(ble_glue->status != BleGlueStatusFusStarted) { | ||||
|     if(ble_glue->status != BleGlueStatusC2Started) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool ret = false; | ||||
|     furi_hal_power_insomnia_enter(); | ||||
|     if(ble_app_init()) { | ||||
|         FURI_LOG_I(TAG, "Radio stack started"); | ||||
|         ble_glue->status = BleGlueStatusRadioStackStarted; | ||||
|         ble_glue->status = BleGlueStatusRadioStackRunning; | ||||
|         ret = true; | ||||
|         if(SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7) == SHCI_Success) { | ||||
|             FURI_LOG_I(TAG, "Flash activity control switched to SEM7"); | ||||
| @ -157,7 +213,6 @@ bool ble_glue_start() { | ||||
|         ble_glue->status = BleGlueStatusRadioStackMissing; | ||||
|         ble_app_thread_stop(); | ||||
|     } | ||||
|     furi_hal_power_insomnia_exit(); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| @ -167,7 +222,7 @@ bool ble_glue_is_alive() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return ble_glue->status >= BleGlueStatusFusStarted; | ||||
|     return ble_glue->status >= BleGlueStatusC2Started; | ||||
| } | ||||
| 
 | ||||
| bool ble_glue_is_radio_stack_ready() { | ||||
| @ -175,26 +230,42 @@ bool ble_glue_is_radio_stack_ready() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return ble_glue->status == BleGlueStatusRadioStackStarted; | ||||
|     return ble_glue->status == BleGlueStatusRadioStackRunning; | ||||
| } | ||||
| 
 | ||||
| bool ble_glue_radio_stack_fw_launch_started() { | ||||
|     bool ret = false; | ||||
|     // Get FUS status
 | ||||
|     SHCI_FUS_GetState_ErrorCode_t err_code = 0; | ||||
|     uint8_t state = SHCI_C2_FUS_GetState(&err_code); | ||||
|     if(state == FUS_STATE_VALUE_IDLE) { | ||||
|         // When FUS is running we can't read radio stack version correctly
 | ||||
|         // Trying to start radio stack fw, which leads to reset
 | ||||
|         FURI_LOG_W(TAG, "FUS is running. Restart to launch Radio Stack"); | ||||
| BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode desired_mode) { | ||||
|     furi_check(desired_mode > BleGlueC2ModeUnknown); | ||||
| 
 | ||||
|     if(desired_mode == ble_glue->c2_info.mode) { | ||||
|         return BleGlueCommandResultOK; | ||||
|     } | ||||
| 
 | ||||
|     if((ble_glue->c2_info.mode == BleGlueC2ModeFUS) && (desired_mode == BleGlueC2ModeStack)) { | ||||
|         if((ble_glue->c2_info.VersionMajor == 0) && (ble_glue->c2_info.VersionMinor == 0)) { | ||||
|             FURI_LOG_W(TAG, "Stack isn't installed!"); | ||||
|             return BleGlueCommandResultError; | ||||
|         } | ||||
|         SHCI_CmdStatus_t status = SHCI_C2_FUS_StartWs(); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(TAG, "Failed to start Radio Stack with status: %02X", status); | ||||
|         } else { | ||||
|             ret = true; | ||||
|             return BleGlueCommandResultError; | ||||
|         } | ||||
|         return BleGlueCommandResultRestartPending; | ||||
|     } | ||||
|     return ret; | ||||
|     if((ble_glue->c2_info.mode == BleGlueC2ModeStack) && (desired_mode == BleGlueC2ModeFUS)) { | ||||
|         SHCI_FUS_GetState_ErrorCode_t error_code = 0; | ||||
|         uint8_t fus_state = SHCI_C2_FUS_GetState(&error_code); | ||||
|         FURI_LOG_D(TAG, "FUS state: %X, error = %x", fus_state, error_code); | ||||
|         if(fus_state == SHCI_FUS_CMD_NOT_SUPPORTED) { | ||||
|             // Second call to SHCI_C2_FUS_GetState() restarts whole MCU & boots FUS
 | ||||
|             fus_state = SHCI_C2_FUS_GetState(&error_code); | ||||
|             FURI_LOG_D(TAG, "FUS state#2: %X, error = %x", fus_state, error_code); | ||||
|             return BleGlueCommandResultRestartPending; | ||||
|         } | ||||
|         return BleGlueCommandResultOK; | ||||
|     } | ||||
| 
 | ||||
|     return BleGlueCommandResultError; | ||||
| } | ||||
| 
 | ||||
| static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { | ||||
| @ -221,19 +292,26 @@ static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { | ||||
|  */ | ||||
| static void ble_glue_sys_user_event_callback(void* pPayload) { | ||||
|     UNUSED(pPayload); | ||||
|     /* Traces channel initialization */ | ||||
|     // APPD_EnableCPU2( );
 | ||||
| 
 | ||||
| #ifdef BLE_GLUE_DEBUG | ||||
|     APPD_EnableCPU2(); | ||||
| #endif | ||||
| 
 | ||||
|     TL_AsynchEvt_t* p_sys_event = | ||||
|         (TL_AsynchEvt_t*)(((tSHCI_UserEvtRxParam*)pPayload)->pckt->evtserial.evt.payload); | ||||
| 
 | ||||
|     if(p_sys_event->subevtcode == SHCI_SUB_EVT_CODE_READY) { | ||||
|         FURI_LOG_I(TAG, "Fus started"); | ||||
|         ble_glue->status = BleGlueStatusFusStarted; | ||||
|         furi_hal_power_insomnia_exit(); | ||||
|         FURI_LOG_I(TAG, "Core2 started"); | ||||
|         SHCI_C2_Ready_Evt_t* p_c2_ready_evt = (SHCI_C2_Ready_Evt_t*)p_sys_event->payload; | ||||
|         if(p_c2_ready_evt->sysevt_ready_rsp == WIRELESS_FW_RUNNING) { | ||||
|             ble_glue->c2_info.mode = BleGlueC2ModeStack; | ||||
|         } else if(p_c2_ready_evt->sysevt_ready_rsp == FUS_FW_RUNNING) { | ||||
|             ble_glue->c2_info.mode = BleGlueC2ModeFUS; | ||||
|         } | ||||
| 
 | ||||
|         ble_glue->status = BleGlueStatusC2Started; | ||||
|     } else if(p_sys_event->subevtcode == SHCI_SUB_EVT_ERROR_NOTIF) { | ||||
|         FURI_LOG_E(TAG, "Error during initialization"); | ||||
|         furi_hal_power_insomnia_exit(); | ||||
|     } else if(p_sys_event->subevtcode == SHCI_SUB_EVT_BLE_NVM_RAM_UPDATE) { | ||||
|         SHCI_C2_BleNvmRamUpdate_Evt_t* p_sys_ble_nvm_ram_update_event = | ||||
|             (SHCI_C2_BleNvmRamUpdate_Evt_t*)p_sys_event->payload; | ||||
| @ -308,3 +386,61 @@ void shci_cmd_resp_wait(uint32_t timeout) { | ||||
|         osSemaphoreAcquire(ble_glue->shci_sem, osWaitForever); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool ble_glue_reinit_c2() { | ||||
|     return SHCI_C2_Reinit() == SHCI_Success; | ||||
| } | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_stack_delete() { | ||||
|     FURI_LOG_I(TAG, "Erasing stack"); | ||||
|     SHCI_CmdStatus_t erase_stat = SHCI_C2_FUS_FwDelete(); | ||||
|     FURI_LOG_I(TAG, "Cmd res = %x", erase_stat); | ||||
|     if(erase_stat == SHCI_Success) { | ||||
|         return BleGlueCommandResultOperationOngoing; | ||||
|     } | ||||
|     ble_glue_fus_get_status(); | ||||
|     return BleGlueCommandResultError; | ||||
| } | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_addr) { | ||||
|     FURI_LOG_I(TAG, "Installing stack"); | ||||
|     SHCI_CmdStatus_t write_stat = SHCI_C2_FUS_FwUpgrade(src_addr, dst_addr); | ||||
|     FURI_LOG_I(TAG, "Cmd res = %x", write_stat); | ||||
|     if(write_stat == SHCI_Success) { | ||||
|         return BleGlueCommandResultOperationOngoing; | ||||
|     } | ||||
|     ble_glue_fus_get_status(); | ||||
|     return BleGlueCommandResultError; | ||||
| } | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_get_status() { | ||||
|     furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS); | ||||
|     SHCI_FUS_GetState_ErrorCode_t error_code = 0; | ||||
|     uint8_t fus_state = SHCI_C2_FUS_GetState(&error_code); | ||||
|     FURI_LOG_I(TAG, "FUS state: %x, error: %x", fus_state, error_code); | ||||
|     if((error_code != 0) || (fus_state == FUS_STATE_VALUE_ERROR)) { | ||||
|         return BleGlueCommandResultError; | ||||
|     } else if( | ||||
|         (fus_state >= FUS_STATE_VALUE_FW_UPGRD_ONGOING) && | ||||
|         (fus_state <= FUS_STATE_VALUE_SERVICE_ONGOING_END)) { | ||||
|         return BleGlueCommandResultOperationOngoing; | ||||
|     } | ||||
|     return BleGlueCommandResultOK; | ||||
| } | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_wait_operation() { | ||||
|     furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS); | ||||
|     bool wip; | ||||
|     do { | ||||
|         BleGlueCommandResult fus_status = ble_glue_fus_get_status(); | ||||
|         if(fus_status == BleGlueCommandResultError) { | ||||
|             return BleGlueCommandResultError; | ||||
|         } | ||||
|         wip = fus_status == BleGlueCommandResultOperationOngoing; | ||||
|         if(wip) { | ||||
|             osDelay(20); | ||||
|         } | ||||
|     } while(wip); | ||||
| 
 | ||||
|     return BleGlueCommandResultOK; | ||||
| } | ||||
|  | ||||
| @ -2,12 +2,53 @@ | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <shci/shci.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef enum { | ||||
|     BleGlueC2ModeUnknown = 0, | ||||
|     BleGlueC2ModeFUS, | ||||
|     BleGlueC2ModeStack, | ||||
| } BleGlueC2Mode; | ||||
| 
 | ||||
| typedef struct { | ||||
|     BleGlueC2Mode mode; | ||||
|     /**
 | ||||
|      * Wireless Info | ||||
|      */ | ||||
|     uint8_t VersionMajor; | ||||
|     uint8_t VersionMinor; | ||||
|     uint8_t VersionSub; | ||||
|     uint8_t VersionBranch; | ||||
|     uint8_t VersionReleaseType; | ||||
|     uint8_t MemorySizeSram2B; /*< Multiple of 1K */ | ||||
|     uint8_t MemorySizeSram2A; /*< Multiple of 1K */ | ||||
|     uint8_t MemorySizeSram1; /*< Multiple of 1K */ | ||||
|     uint8_t MemorySizeFlash; /*< Multiple of 4K */ | ||||
|     uint8_t StackType; | ||||
|     /**
 | ||||
|      * Fus Info | ||||
|      */ | ||||
|     uint8_t FusVersionMajor; | ||||
|     uint8_t FusVersionMinor; | ||||
|     uint8_t FusVersionSub; | ||||
|     uint8_t FusMemorySizeSram2B; /*< Multiple of 1K */ | ||||
|     uint8_t FusMemorySizeSram2A; /*< Multiple of 1K */ | ||||
|     uint8_t FusMemorySizeFlash; /*< Multiple of 4K */ | ||||
| } BleGlueC2Info; | ||||
| 
 | ||||
| typedef enum { | ||||
|     // Stage 1: core2 startup and FUS
 | ||||
|     BleGlueStatusStartup, | ||||
|     BleGlueStatusBroken, | ||||
|     BleGlueStatusC2Started, | ||||
|     // Stage 2: radio stack
 | ||||
|     BleGlueStatusRadioStackRunning, | ||||
|     BleGlueStatusRadioStackMissing | ||||
| } BleGlueStatus; | ||||
| 
 | ||||
| typedef void ( | ||||
|     *BleGlueKeyStorageChangedCallback)(uint8_t* change_addr_start, uint16_t size, void* context); | ||||
| 
 | ||||
| @ -26,7 +67,15 @@ bool ble_glue_start(); | ||||
|  */ | ||||
| bool ble_glue_is_alive(); | ||||
| 
 | ||||
| bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info); | ||||
| /** Waits for C2 to reports its mode to callback
 | ||||
|  * | ||||
|  * @return     true if it reported before reaching timeout | ||||
|  */ | ||||
| bool ble_glue_wait_for_c2_start(int32_t timeout); | ||||
| 
 | ||||
| BleGlueStatus ble_glue_get_c2_status(); | ||||
| 
 | ||||
| const BleGlueC2Info* ble_glue_get_c2_info(); | ||||
| 
 | ||||
| /** Is core2 radio stack present and ready
 | ||||
|  * | ||||
| @ -46,12 +95,30 @@ void ble_glue_set_key_storage_changed_callback( | ||||
| /** Stop SHCI thread */ | ||||
| void ble_glue_thread_stop(); | ||||
| 
 | ||||
| bool ble_glue_reinit_c2(); | ||||
| 
 | ||||
| typedef enum { | ||||
|     BleGlueCommandResultUnknown, | ||||
|     BleGlueCommandResultOK, | ||||
|     BleGlueCommandResultError, | ||||
|     BleGlueCommandResultRestartPending, | ||||
|     BleGlueCommandResultOperationOngoing, | ||||
| } BleGlueCommandResult; | ||||
| 
 | ||||
| /** Restart MCU to launch radio stack firmware if necessary
 | ||||
|  * | ||||
|  * @return      true on radio stack start command | ||||
|  */ | ||||
| bool ble_glue_radio_stack_fw_launch_started(); | ||||
| BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode mode); | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_stack_delete(); | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_addr); | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_get_status(); | ||||
| 
 | ||||
| BleGlueCommandResult ble_glue_fus_wait_operation(); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| #endif | ||||
| @ -115,7 +115,6 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { | ||||
|         if(gap->enable_adv) { | ||||
|             // Restart advertising
 | ||||
|             gap_advertise_start(GapStateAdvFast); | ||||
|             furi_hal_power_insomnia_exit(); | ||||
|         } | ||||
|         GapEvent event = {.type = GapEventTypeDisconnected}; | ||||
|         gap->on_event_cb(event, gap->context); | ||||
| @ -151,8 +150,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|         case EVT_LE_CONN_COMPLETE: | ||||
|             furi_hal_power_insomnia_enter(); | ||||
|         case EVT_LE_CONN_COMPLETE: { | ||||
|             hci_le_connection_complete_event_rp0* event = | ||||
|                 (hci_le_connection_complete_event_rp0*)meta_evt->data; | ||||
|             gap->connection_params.conn_interval = event->Conn_Interval; | ||||
| @ -169,7 +167,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { | ||||
|             gap_verify_connection_parameters(gap); | ||||
|             // Start pairing by sending security request
 | ||||
|             aci_gap_slave_security_req(event->Connection_Handle); | ||||
|             break; | ||||
|         } break; | ||||
| 
 | ||||
|         case EVT_LE_ADVERTISING_REPORT: { | ||||
|             if(gap_scan) { | ||||
| @ -414,7 +412,9 @@ static void gap_advertise_start(GapState new_state) { | ||||
|         // Stop advertising
 | ||||
|         status = aci_gap_set_non_discoverable(); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(TAG, "Stop Advertising Failed, result: %d", status); | ||||
|             FURI_LOG_E(TAG, "set_non_discoverable failed %d", status); | ||||
|         } else { | ||||
|             FURI_LOG_D(TAG, "set_non_discoverable success"); | ||||
|         } | ||||
|     } | ||||
|     // Configure advertising
 | ||||
| @ -431,7 +431,7 @@ static void gap_advertise_start(GapState new_state) { | ||||
|         0, | ||||
|         0); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(TAG, "Set discoverable err: %d", status); | ||||
|         FURI_LOG_E(TAG, "set_discoverable failed %d", status); | ||||
|     } | ||||
|     gap->state = new_state; | ||||
|     GapEvent event = {.type = GapEventTypeStartAdvertising}; | ||||
| @ -440,14 +440,25 @@ static void gap_advertise_start(GapState new_state) { | ||||
| } | ||||
| 
 | ||||
| static void gap_advertise_stop() { | ||||
|     tBleStatus ret; | ||||
|     if(gap->state > GapStateIdle) { | ||||
|         if(gap->state == GapStateConnected) { | ||||
|             // Terminate connection
 | ||||
|             aci_gap_terminate(gap->service.connection_handle, 0x13); | ||||
|             ret = aci_gap_terminate(gap->service.connection_handle, 0x13); | ||||
|             if(ret != BLE_STATUS_SUCCESS) { | ||||
|                 FURI_LOG_E(TAG, "terminate failed %d", ret); | ||||
|             } else { | ||||
|                 FURI_LOG_D(TAG, "terminate success"); | ||||
|             } | ||||
|         } | ||||
|         // Stop advertising
 | ||||
|         osTimerStop(gap->advertise_timer); | ||||
|         aci_gap_set_non_discoverable(); | ||||
|         ret = aci_gap_set_non_discoverable(); | ||||
|         if(ret != BLE_STATUS_SUCCESS) { | ||||
|             FURI_LOG_E(TAG, "set_non_discoverable failed %d", ret); | ||||
|         } else { | ||||
|             FURI_LOG_D(TAG, "set_non_discoverable success"); | ||||
|         } | ||||
|         gap->state = GapStateIdle; | ||||
|     } | ||||
|     GapEvent event = {.type = GapEventTypeStopAdvertising}; | ||||
|  | ||||
| @ -10,10 +10,10 @@ void furi_hal_init_early() { | ||||
|     furi_hal_clock_init_early(); | ||||
|     furi_hal_delay_init(); | ||||
| 
 | ||||
|     furi_hal_os_init(); | ||||
| 
 | ||||
|     furi_hal_resources_init_early(); | ||||
| 
 | ||||
|     furi_hal_os_init(); | ||||
| 
 | ||||
|     furi_hal_spi_init_early(); | ||||
| 
 | ||||
|     furi_hal_i2c_init_early(); | ||||
| @ -55,7 +55,6 @@ void furi_hal_init() { | ||||
|     FURI_LOG_I(TAG, "Speaker OK"); | ||||
| 
 | ||||
|     furi_hal_crypto_init(); | ||||
|     furi_hal_crc_init(true); | ||||
| 
 | ||||
|     // USB
 | ||||
| #ifndef FURI_RAM_EXEC | ||||
|  | ||||
| @ -16,6 +16,9 @@ | ||||
| #define FURI_HAL_BT_DEFAULT_MAC_ADDR \ | ||||
|     { 0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72 } | ||||
| 
 | ||||
| /* Time, in ms, to wait for mode transition before crashing */ | ||||
| #define C2_MODE_SWITCH_TIMEOUT 10000 | ||||
| 
 | ||||
| osMutexId_t furi_hal_bt_core2_mtx = NULL; | ||||
| static FuriHalBtStack furi_hal_bt_stack = FuriHalBtStackUnknown; | ||||
| 
 | ||||
| @ -99,7 +102,7 @@ void furi_hal_bt_unlock_core2() { | ||||
|     furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK); | ||||
| } | ||||
| 
 | ||||
| static bool furi_hal_bt_radio_stack_is_supported(WirelessFwInfo_t* info) { | ||||
| static bool furi_hal_bt_radio_stack_is_supported(const BleGlueC2Info* info) { | ||||
|     bool supported = false; | ||||
|     if(info->StackType == INFO_STACK_TYPE_BLE_HCI) { | ||||
|         furi_hal_bt_stack = FuriHalBtStackHciLayer; | ||||
| @ -128,21 +131,21 @@ bool furi_hal_bt_start_radio_stack() { | ||||
|     } | ||||
| 
 | ||||
|     do { | ||||
|         // Wait until FUS is started or timeout
 | ||||
|         WirelessFwInfo_t info = {}; | ||||
|         if(!ble_glue_wait_for_fus_start(&info)) { | ||||
|             FURI_LOG_E(TAG, "FUS start failed"); | ||||
|             LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); | ||||
|         // Wait until C2 is started or timeout
 | ||||
|         if(!ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)) { | ||||
|             FURI_LOG_E(TAG, "Core2 start failed"); | ||||
|             ble_glue_thread_stop(); | ||||
|             break; | ||||
|         } | ||||
|         // If FUS is running, start radio stack fw
 | ||||
|         if(ble_glue_radio_stack_fw_launch_started()) { | ||||
|             // If FUS is running do nothing and wait for system reset
 | ||||
|             furi_crash("Waiting for FUS to launch radio stack firmware"); | ||||
| 
 | ||||
|         // If C2 is running, start radio stack fw
 | ||||
|         if(!furi_hal_bt_ensure_c2_mode(BleGlueC2ModeStack)) { | ||||
|             break; | ||||
|         } | ||||
|         // Check weather we support radio stack
 | ||||
|         if(!furi_hal_bt_radio_stack_is_supported(&info)) { | ||||
| 
 | ||||
|         // Check whether we support radio stack
 | ||||
|         const BleGlueC2Info* c2_info = ble_glue_get_c2_info(); | ||||
|         if(!furi_hal_bt_radio_stack_is_supported(c2_info)) { | ||||
|             FURI_LOG_E(TAG, "Unsupported radio stack"); | ||||
|             // Don't stop SHCI for crypto enclave support
 | ||||
|             break; | ||||
| @ -150,7 +153,6 @@ bool furi_hal_bt_start_radio_stack() { | ||||
|         // Starting radio stack
 | ||||
|         if(!ble_glue_start()) { | ||||
|             FURI_LOG_E(TAG, "Failed to start radio stack"); | ||||
|             LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); | ||||
|             ble_glue_thread_stop(); | ||||
|             ble_app_thread_stop(); | ||||
|             break; | ||||
| @ -217,27 +219,39 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| void furi_hal_bt_reinit() { | ||||
|     FURI_LOG_I(TAG, "Disconnect and stop advertising"); | ||||
|     furi_hal_bt_stop_advertising(); | ||||
| 
 | ||||
|     FURI_LOG_I(TAG, "Stop current profile services"); | ||||
|     current_profile->stop(); | ||||
| 
 | ||||
|     // Magic happens here
 | ||||
|     hci_reset(); | ||||
| 
 | ||||
|     FURI_LOG_I(TAG, "Stop BLE related RTOS threads"); | ||||
|     ble_app_thread_stop(); | ||||
|     gap_thread_stop(); | ||||
| 
 | ||||
|     FURI_LOG_I(TAG, "Reset SHCI"); | ||||
|     furi_check(ble_glue_reinit_c2()); | ||||
| 
 | ||||
|     osDelay(100); | ||||
|     ble_glue_thread_stop(); | ||||
| 
 | ||||
|     FURI_LOG_I(TAG, "Start BT initialization"); | ||||
|     furi_hal_bt_init(); | ||||
| 
 | ||||
|     furi_hal_bt_start_radio_stack(); | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) { | ||||
|     furi_assert(event_cb); | ||||
|     furi_assert(profile < FuriHalBtProfileNumber); | ||||
|     bool ret = true; | ||||
| 
 | ||||
|     FURI_LOG_I(TAG, "Stop current profile services"); | ||||
|     current_profile->stop(); | ||||
|     FURI_LOG_I(TAG, "Disconnect and stop advertising"); | ||||
|     furi_hal_bt_stop_advertising(); | ||||
|     FURI_LOG_I(TAG, "Shutdow 2nd core"); | ||||
|     LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); | ||||
|     FURI_LOG_I(TAG, "Stop BLE related RTOS threads"); | ||||
|     ble_app_thread_stop(); | ||||
|     gap_thread_stop(); | ||||
|     FURI_LOG_I(TAG, "Reset SHCI"); | ||||
|     SHCI_C2_Reinit(); | ||||
|     osDelay(100); | ||||
|     ble_glue_thread_stop(); | ||||
|     FURI_LOG_I(TAG, "Start BT initialization"); | ||||
|     furi_hal_bt_init(); | ||||
|     furi_hal_bt_start_radio_stack(); | ||||
|     furi_hal_bt_reinit(); | ||||
| 
 | ||||
|     ret = furi_hal_bt_start_app(profile, event_cb, context); | ||||
|     if(ret) { | ||||
|         current_profile = &profile_config[profile]; | ||||
| @ -404,3 +418,18 @@ void furi_hal_bt_stop_scan() { | ||||
|         gap_stop_scan(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode) { | ||||
|     BleGlueCommandResult fw_start_res = ble_glue_force_c2_mode(mode); | ||||
|     if(fw_start_res == BleGlueCommandResultOK) { | ||||
|         return true; | ||||
|     } else if(fw_start_res == BleGlueCommandResultRestartPending) { | ||||
|         // Do nothing and wait for system reset
 | ||||
|         osDelay(C2_MODE_SWITCH_TIMEOUT); | ||||
|         furi_crash("Waiting for FUS->radio stack transition"); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     FURI_LOG_E(TAG, "Failed to switch C2 mode: %d", fw_start_res); | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| @ -204,6 +204,8 @@ void furi_hal_clock_switch_to_hsi() { | ||||
| 
 | ||||
|     while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSI) | ||||
|         ; | ||||
| 
 | ||||
|     LL_FLASH_SetLatency(LL_FLASH_LATENCY_1); | ||||
| } | ||||
| 
 | ||||
| void furi_hal_clock_switch_to_pll() { | ||||
| @ -215,6 +217,8 @@ void furi_hal_clock_switch_to_pll() { | ||||
|     while(!LL_RCC_PLL_IsReady()) | ||||
|         ; | ||||
| 
 | ||||
|     LL_FLASH_SetLatency(LL_FLASH_LATENCY_3); | ||||
| 
 | ||||
|     LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL); | ||||
|     LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSE); | ||||
| 
 | ||||
|  | ||||
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