Merge branch 'dev' into release-candidate
This commit is contained in:
		
						commit
						f3603e3c04
					
				
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@ -1,2 +1 @@
 | 
				
			|||||||
* text=auto
 | 
					* text=auto
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										43
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										43
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@ -103,6 +103,32 @@ jobs:
 | 
				
			|||||||
                -o firmware/.obj/${TARGET}/full.hex -Intel
 | 
					                -o firmware/.obj/${TARGET}/full.hex -Intel
 | 
				
			||||||
            done
 | 
					            done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: 'Generate full dfu file'
 | 
				
			||||||
 | 
					        if: ${{ !github.event.pull_request.head.repo.fork }}
 | 
				
			||||||
 | 
					        uses: ./.github/actions/docker
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          run: |
 | 
				
			||||||
 | 
					            for TARGET in ${TARGETS}
 | 
				
			||||||
 | 
					            do
 | 
				
			||||||
 | 
					              hex2dfu \
 | 
				
			||||||
 | 
					                -i firmware/.obj/${TARGET}/full.hex \
 | 
				
			||||||
 | 
					                -o artifacts/flipper-z-${TARGET}-full-${{steps.names.outputs.suffix}}.dfu \
 | 
				
			||||||
 | 
					                -l "Flipper Zero $(echo $TARGET | tr a-z A-Z)"
 | 
				
			||||||
 | 
					            done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: 'Generate full json file'
 | 
				
			||||||
 | 
					        if: ${{ !github.event.pull_request.head.repo.fork }}
 | 
				
			||||||
 | 
					        uses: ./.github/actions/docker
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          run: |
 | 
				
			||||||
 | 
					            for TARGET in ${TARGETS}
 | 
				
			||||||
 | 
					            do
 | 
				
			||||||
 | 
					              jq -s '.[0] * .[1]' \
 | 
				
			||||||
 | 
					                bootloader/.obj/${TARGET}/bootloader.json \
 | 
				
			||||||
 | 
					                firmware/.obj/${TARGET}/firmware.json  \
 | 
				
			||||||
 | 
					                > artifacts/flipper-z-${TARGET}-full-${{steps.names.outputs.suffix}}.json
 | 
				
			||||||
 | 
					            done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: 'Move upload files'
 | 
					      - name: 'Move upload files'
 | 
				
			||||||
        if: ${{ !github.event.pull_request.head.repo.fork }}
 | 
					        if: ${{ !github.event.pull_request.head.repo.fork }}
 | 
				
			||||||
        uses: ./.github/actions/docker
 | 
					        uses: ./.github/actions/docker
 | 
				
			||||||
@ -116,25 +142,16 @@ jobs:
 | 
				
			|||||||
                artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.bin
 | 
					                artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.bin
 | 
				
			||||||
              mv bootloader/.obj/${TARGET}/bootloader.elf \
 | 
					              mv bootloader/.obj/${TARGET}/bootloader.elf \
 | 
				
			||||||
                artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.elf
 | 
					                artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.elf
 | 
				
			||||||
 | 
					              mv bootloader/.obj/${TARGET}/bootloader.json \
 | 
				
			||||||
 | 
					                artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.json
 | 
				
			||||||
              mv firmware/.obj/${TARGET}/firmware.dfu \
 | 
					              mv firmware/.obj/${TARGET}/firmware.dfu \
 | 
				
			||||||
                artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.dfu
 | 
					                artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.dfu
 | 
				
			||||||
              mv firmware/.obj/${TARGET}/firmware.bin \
 | 
					              mv firmware/.obj/${TARGET}/firmware.bin \
 | 
				
			||||||
                artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.bin
 | 
					                artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.bin
 | 
				
			||||||
              mv firmware/.obj/${TARGET}/firmware.elf \
 | 
					              mv firmware/.obj/${TARGET}/firmware.elf \
 | 
				
			||||||
                artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.elf
 | 
					                artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.elf
 | 
				
			||||||
            done
 | 
					              mv firmware/.obj/${TARGET}/firmware.json \
 | 
				
			||||||
 | 
					                artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.json
 | 
				
			||||||
      - name: 'Generate full dfu file'
 | 
					 | 
				
			||||||
        if: ${{ !github.event.pull_request.head.repo.fork }}
 | 
					 | 
				
			||||||
        uses: ./.github/actions/docker
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          run: |
 | 
					 | 
				
			||||||
            for TARGET in ${TARGETS}
 | 
					 | 
				
			||||||
            do
 | 
					 | 
				
			||||||
              hex2dfu \
 | 
					 | 
				
			||||||
                -i firmware/.obj/${TARGET}/full.hex \
 | 
					 | 
				
			||||||
                -o artifacts/flipper-z-${TARGET}-full-${{steps.names.outputs.suffix}}.dfu \
 | 
					 | 
				
			||||||
                -l "Flipper Zero $(echo $TARGET | tr a-z A-Z)"
 | 
					 | 
				
			||||||
            done
 | 
					            done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: 'Full flash asssembly: bootloader as base'
 | 
					      - name: 'Full flash asssembly: bootloader as base'
 | 
				
			||||||
 | 
				
			|||||||
@ -129,11 +129,11 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage*
 | 
				
			|||||||
    return result;
 | 
					    return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static DialogMessageButton boot_version_screen(DialogsApp* dialogs, DialogMessage* message) {
 | 
					static DialogMessageButton bootloader_version_screen(DialogsApp* dialogs, DialogMessage* message) {
 | 
				
			||||||
    DialogMessageButton result;
 | 
					    DialogMessageButton result;
 | 
				
			||||||
    string_t buffer;
 | 
					    string_t buffer;
 | 
				
			||||||
    string_init(buffer);
 | 
					    string_init(buffer);
 | 
				
			||||||
    const Version* ver = furi_hal_version_get_boot_version();
 | 
					    const Version* ver = furi_hal_version_get_bootloader_version();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(!ver) {
 | 
					    if(!ver) {
 | 
				
			||||||
        string_cat_printf(buffer, "No info\n");
 | 
					        string_cat_printf(buffer, "No info\n");
 | 
				
			||||||
@ -167,7 +167,7 @@ const AboutDialogScreen about_screens[] = {
 | 
				
			|||||||
    icon2_screen,
 | 
					    icon2_screen,
 | 
				
			||||||
    hw_version_screen,
 | 
					    hw_version_screen,
 | 
				
			||||||
    fw_version_screen,
 | 
					    fw_version_screen,
 | 
				
			||||||
    boot_version_screen};
 | 
					    bootloader_version_screen};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const size_t about_screens_count = sizeof(about_screens) / sizeof(AboutDialogScreen);
 | 
					const size_t about_screens_count = sizeof(about_screens) / sizeof(AboutDialogScreen);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -84,7 +84,7 @@ static void bt_on_data_received_callback(uint8_t* data, uint16_t size, void* con
 | 
				
			|||||||
    furi_assert(context);
 | 
					    furi_assert(context);
 | 
				
			||||||
    Bt* bt = context;
 | 
					    Bt* bt = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    size_t bytes_processed = rpc_feed_bytes(bt->rpc_session, data, size, 1000);
 | 
					    size_t bytes_processed = rpc_session_feed(bt->rpc_session, data, size, 1000);
 | 
				
			||||||
    if(bytes_processed != size) {
 | 
					    if(bytes_processed != size) {
 | 
				
			||||||
        FURI_LOG_E(BT_SERVICE_TAG, "Only %d of %d bytes processed by RPC", bytes_processed, size);
 | 
					        FURI_LOG_E(BT_SERVICE_TAG, "Only %d of %d bytes processed by RPC", bytes_processed, size);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -129,8 +129,9 @@ static void bt_on_gap_event_callback(BleEvent event, void* context) {
 | 
				
			|||||||
        furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
 | 
					        furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
 | 
				
			||||||
        // Open RPC session
 | 
					        // Open RPC session
 | 
				
			||||||
        FURI_LOG_I(BT_SERVICE_TAG, "Open RPC connection");
 | 
					        FURI_LOG_I(BT_SERVICE_TAG, "Open RPC connection");
 | 
				
			||||||
        bt->rpc_session = rpc_open_session(bt->rpc);
 | 
					        bt->rpc_session = rpc_session_open(bt->rpc);
 | 
				
			||||||
        rpc_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback, bt);
 | 
					        rpc_session_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback);
 | 
				
			||||||
 | 
					        rpc_session_set_context(bt->rpc_session, bt);
 | 
				
			||||||
        furi_hal_bt_set_data_event_callbacks(
 | 
					        furi_hal_bt_set_data_event_callbacks(
 | 
				
			||||||
            bt_on_data_received_callback, bt_on_data_sent_callback, bt);
 | 
					            bt_on_data_received_callback, bt_on_data_sent_callback, bt);
 | 
				
			||||||
        // Update battery level
 | 
					        // Update battery level
 | 
				
			||||||
@ -142,7 +143,7 @@ static void bt_on_gap_event_callback(BleEvent event, void* context) {
 | 
				
			|||||||
    } else if(event.type == BleEventTypeDisconnected) {
 | 
					    } else if(event.type == BleEventTypeDisconnected) {
 | 
				
			||||||
        FURI_LOG_I(BT_SERVICE_TAG, "Close RPC connection");
 | 
					        FURI_LOG_I(BT_SERVICE_TAG, "Close RPC connection");
 | 
				
			||||||
        if(bt->rpc_session) {
 | 
					        if(bt->rpc_session) {
 | 
				
			||||||
            rpc_close_session(bt->rpc_session);
 | 
					            rpc_session_close(bt->rpc_session);
 | 
				
			||||||
            bt->rpc_session = NULL;
 | 
					            bt->rpc_session = NULL;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else if(event.type == BleEventTypeStartAdvertising) {
 | 
					    } else if(event.type == BleEventTypeStartAdvertising) {
 | 
				
			||||||
 | 
				
			|||||||
@ -263,7 +263,7 @@ static void cli_handle_autocomplete(Cli* cli) {
 | 
				
			|||||||
        cli->cursor_position = string_size(cli->line);
 | 
					        cli->cursor_position = string_size(cli->line);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // Cleanup
 | 
					    // Cleanup
 | 
				
			||||||
    string_clean(common);
 | 
					    string_clear(common);
 | 
				
			||||||
    // Show prompt
 | 
					    // Show prompt
 | 
				
			||||||
    cli_prompt(cli);
 | 
					    cli_prompt(cli);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -56,13 +56,13 @@ static const uint8_t enclave_signature_expected[ENCLAVE_SIGNATURE_KEY_SLOTS][ENC
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
void cli_command_device_info(Cli* cli, string_t args, void* context) {
 | 
					void cli_command_device_info(Cli* cli, string_t args, void* context) {
 | 
				
			||||||
    // Device Info version
 | 
					    // Device Info version
 | 
				
			||||||
    printf("device_info_major   : %d\r\n", 1);
 | 
					    printf("device_info_major       : %d\r\n", 2);
 | 
				
			||||||
    printf("device_info_minor   : %d\r\n", 0);
 | 
					    printf("device_info_minor       : %d\r\n", 0);
 | 
				
			||||||
    // Model name
 | 
					    // Model name
 | 
				
			||||||
    printf("hardware_model      : %s\r\n", furi_hal_version_get_model_name());
 | 
					    printf("hardware_model          : %s\r\n", furi_hal_version_get_model_name());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Unique ID
 | 
					    // Unique ID
 | 
				
			||||||
    printf("hardware_uid        : ");
 | 
					    printf("hardware_uid            : ");
 | 
				
			||||||
    const uint8_t* uid = furi_hal_version_uid();
 | 
					    const uint8_t* uid = furi_hal_version_uid();
 | 
				
			||||||
    for(size_t i = 0; i < furi_hal_version_uid_size(); i++) {
 | 
					    for(size_t i = 0; i < furi_hal_version_uid_size(); i++) {
 | 
				
			||||||
        printf("%02X", uid[i]);
 | 
					        printf("%02X", uid[i]);
 | 
				
			||||||
@ -70,69 +70,69 @@ void cli_command_device_info(Cli* cli, string_t args, void* context) {
 | 
				
			|||||||
    printf("\r\n");
 | 
					    printf("\r\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // OTP Revision
 | 
					    // OTP Revision
 | 
				
			||||||
    printf("hardware_otp_ver    : %d\r\n", furi_hal_version_get_otp_version());
 | 
					    printf("hardware_otp_ver        : %d\r\n", furi_hal_version_get_otp_version());
 | 
				
			||||||
    printf("hardware_timestamp  : %lu\r\n", furi_hal_version_get_hw_timestamp());
 | 
					    printf("hardware_timestamp      : %lu\r\n", furi_hal_version_get_hw_timestamp());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Board Revision
 | 
					    // Board Revision
 | 
				
			||||||
    printf("hardware_ver        : %d\r\n", furi_hal_version_get_hw_version());
 | 
					    printf("hardware_ver            : %d\r\n", furi_hal_version_get_hw_version());
 | 
				
			||||||
    printf("hardware_target     : %d\r\n", furi_hal_version_get_hw_target());
 | 
					    printf("hardware_target         : %d\r\n", furi_hal_version_get_hw_target());
 | 
				
			||||||
    printf("hardware_body       : %d\r\n", furi_hal_version_get_hw_body());
 | 
					    printf("hardware_body           : %d\r\n", furi_hal_version_get_hw_body());
 | 
				
			||||||
    printf("hardware_connect    : %d\r\n", furi_hal_version_get_hw_connect());
 | 
					    printf("hardware_connect        : %d\r\n", furi_hal_version_get_hw_connect());
 | 
				
			||||||
    printf("hardware_display    : %d\r\n", furi_hal_version_get_hw_display());
 | 
					    printf("hardware_display        : %d\r\n", furi_hal_version_get_hw_display());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Board Personification
 | 
					    // Board Personification
 | 
				
			||||||
    printf("hardware_color      : %d\r\n", furi_hal_version_get_hw_color());
 | 
					    printf("hardware_color          : %d\r\n", furi_hal_version_get_hw_color());
 | 
				
			||||||
    printf("hardware_region     : %d\r\n", furi_hal_version_get_hw_region());
 | 
					    printf("hardware_region         : %d\r\n", furi_hal_version_get_hw_region());
 | 
				
			||||||
    const char* name = furi_hal_version_get_name_ptr();
 | 
					    const char* name = furi_hal_version_get_name_ptr();
 | 
				
			||||||
    if(name) {
 | 
					    if(name) {
 | 
				
			||||||
        printf("hardware_name       : %s\r\n", name);
 | 
					        printf("hardware_name           : %s\r\n", name);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Bootloader Version
 | 
					    // Bootloader Version
 | 
				
			||||||
    const Version* boot_version = furi_hal_version_get_boot_version();
 | 
					    const Version* bootloader_version = furi_hal_version_get_bootloader_version();
 | 
				
			||||||
    if(boot_version) {
 | 
					    if(bootloader_version) {
 | 
				
			||||||
        printf("boot_commit         : %s\r\n", version_get_githash(boot_version));
 | 
					        printf("bootloader_commit       : %s\r\n", version_get_githash(bootloader_version));
 | 
				
			||||||
        printf("boot_branch         : %s\r\n", version_get_gitbranch(boot_version));
 | 
					        printf("bootloader_branch       : %s\r\n", version_get_gitbranch(bootloader_version));
 | 
				
			||||||
        printf("boot_branch_num     : %s\r\n", version_get_gitbranchnum(boot_version));
 | 
					        printf("bootloader_branch_num   : %s\r\n", version_get_gitbranchnum(bootloader_version));
 | 
				
			||||||
        printf("boot_version        : %s\r\n", version_get_version(boot_version));
 | 
					        printf("bootloader_version      : %s\r\n", version_get_version(bootloader_version));
 | 
				
			||||||
        printf("boot_build_date     : %s\r\n", version_get_builddate(boot_version));
 | 
					        printf("bootloader_build_date   : %s\r\n", version_get_builddate(bootloader_version));
 | 
				
			||||||
        printf("boot_target         : %d\r\n", version_get_target(boot_version));
 | 
					        printf("bootloader_target       : %d\r\n", version_get_target(bootloader_version));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Firmware version
 | 
					    // Firmware version
 | 
				
			||||||
    const Version* firmware_version = furi_hal_version_get_firmware_version();
 | 
					    const Version* firmware_version = furi_hal_version_get_firmware_version();
 | 
				
			||||||
    if(firmware_version) {
 | 
					    if(firmware_version) {
 | 
				
			||||||
        printf("firmware_commit     : %s\r\n", version_get_githash(firmware_version));
 | 
					        printf("firmware_commit         : %s\r\n", version_get_githash(firmware_version));
 | 
				
			||||||
        printf("firmware_branch     : %s\r\n", version_get_gitbranch(firmware_version));
 | 
					        printf("firmware_branch         : %s\r\n", version_get_gitbranch(firmware_version));
 | 
				
			||||||
        printf("firmware_branch_num : %s\r\n", version_get_gitbranchnum(firmware_version));
 | 
					        printf("firmware_branch_num     : %s\r\n", version_get_gitbranchnum(firmware_version));
 | 
				
			||||||
        printf("firmware_version    : %s\r\n", version_get_version(firmware_version));
 | 
					        printf("firmware_version        : %s\r\n", version_get_version(firmware_version));
 | 
				
			||||||
        printf("firmware_build_date : %s\r\n", version_get_builddate(firmware_version));
 | 
					        printf("firmware_build_date     : %s\r\n", version_get_builddate(firmware_version));
 | 
				
			||||||
        printf("firmware_target     : %d\r\n", version_get_target(firmware_version));
 | 
					        printf("firmware_target         : %d\r\n", version_get_target(firmware_version));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    WirelessFwInfo_t pWirelessInfo;
 | 
					    WirelessFwInfo_t pWirelessInfo;
 | 
				
			||||||
    if(furi_hal_bt_is_alive() && SHCI_GetWirelessFwInfo(&pWirelessInfo) == SHCI_Success) {
 | 
					    if(furi_hal_bt_is_alive() && SHCI_GetWirelessFwInfo(&pWirelessInfo) == SHCI_Success) {
 | 
				
			||||||
        printf("radio_alive         : true\r\n");
 | 
					        printf("radio_alive             : true\r\n");
 | 
				
			||||||
        // FUS Info
 | 
					        // FUS Info
 | 
				
			||||||
        printf("radio_fus_major     : %d\r\n", pWirelessInfo.FusVersionMajor);
 | 
					        printf("radio_fus_major         : %d\r\n", pWirelessInfo.FusVersionMajor);
 | 
				
			||||||
        printf("radio_fus_minor     : %d\r\n", pWirelessInfo.FusVersionMinor);
 | 
					        printf("radio_fus_minor         : %d\r\n", pWirelessInfo.FusVersionMinor);
 | 
				
			||||||
        printf("radio_fus_sub       : %d\r\n", pWirelessInfo.FusVersionSub);
 | 
					        printf("radio_fus_sub           : %d\r\n", pWirelessInfo.FusVersionSub);
 | 
				
			||||||
        printf("radio_fus_sram2b    : %dK\r\n", pWirelessInfo.FusMemorySizeSram2B);
 | 
					        printf("radio_fus_sram2b        : %dK\r\n", pWirelessInfo.FusMemorySizeSram2B);
 | 
				
			||||||
        printf("radio_fus_sram2a    : %dK\r\n", pWirelessInfo.FusMemorySizeSram2A);
 | 
					        printf("radio_fus_sram2a        : %dK\r\n", pWirelessInfo.FusMemorySizeSram2A);
 | 
				
			||||||
        printf("radio_fus_flash     : %dK\r\n", pWirelessInfo.FusMemorySizeFlash * 4);
 | 
					        printf("radio_fus_flash         : %dK\r\n", pWirelessInfo.FusMemorySizeFlash * 4);
 | 
				
			||||||
        // Stack Info
 | 
					        // Stack Info
 | 
				
			||||||
        printf("radio_stack_type    : %d\r\n", pWirelessInfo.StackType);
 | 
					        printf("radio_stack_type        : %d\r\n", pWirelessInfo.StackType);
 | 
				
			||||||
        printf("radio_stack_major   : %d\r\n", pWirelessInfo.VersionMajor);
 | 
					        printf("radio_stack_major       : %d\r\n", pWirelessInfo.VersionMajor);
 | 
				
			||||||
        printf("radio_stack_minor   : %d\r\n", pWirelessInfo.VersionMinor);
 | 
					        printf("radio_stack_minor       : %d\r\n", pWirelessInfo.VersionMinor);
 | 
				
			||||||
        printf("radio_stack_sub     : %d\r\n", pWirelessInfo.VersionSub);
 | 
					        printf("radio_stack_sub         : %d\r\n", pWirelessInfo.VersionSub);
 | 
				
			||||||
        printf("radio_stack_branch  : %d\r\n", pWirelessInfo.VersionBranch);
 | 
					        printf("radio_stack_branch      : %d\r\n", pWirelessInfo.VersionBranch);
 | 
				
			||||||
        printf("radio_stack_release : %d\r\n", pWirelessInfo.VersionReleaseType);
 | 
					        printf("radio_stack_release     : %d\r\n", pWirelessInfo.VersionReleaseType);
 | 
				
			||||||
        printf("radio_stack_sram2b  : %dK\r\n", pWirelessInfo.MemorySizeSram2B);
 | 
					        printf("radio_stack_sram2b      : %dK\r\n", pWirelessInfo.MemorySizeSram2B);
 | 
				
			||||||
        printf("radio_stack_sram2a  : %dK\r\n", pWirelessInfo.MemorySizeSram2A);
 | 
					        printf("radio_stack_sram2a      : %dK\r\n", pWirelessInfo.MemorySizeSram2A);
 | 
				
			||||||
        printf("radio_stack_sram1   : %dK\r\n", pWirelessInfo.MemorySizeSram1);
 | 
					        printf("radio_stack_sram1       : %dK\r\n", pWirelessInfo.MemorySizeSram1);
 | 
				
			||||||
        printf("radio_stack_flash   : %dK\r\n", pWirelessInfo.MemorySizeFlash * 4);
 | 
					        printf("radio_stack_flash       : %dK\r\n", pWirelessInfo.MemorySizeFlash * 4);
 | 
				
			||||||
        // Mac address
 | 
					        // Mac address
 | 
				
			||||||
        printf("radio_ble_mac       : ");
 | 
					        printf("radio_ble_mac           : ");
 | 
				
			||||||
        const uint8_t* ble_mac = furi_hal_version_get_ble_mac();
 | 
					        const uint8_t* ble_mac = furi_hal_version_get_ble_mac();
 | 
				
			||||||
        for(size_t i = 0; i < 6; i++) {
 | 
					        for(size_t i = 0; i < 6; i++) {
 | 
				
			||||||
            printf("%02X", ble_mac[i]);
 | 
					            printf("%02X", ble_mac[i]);
 | 
				
			||||||
@ -154,12 +154,12 @@ void cli_command_device_info(Cli* cli, string_t args, void* context) {
 | 
				
			|||||||
                furi_hal_crypto_store_unload_key(key_slot + 1);
 | 
					                furi_hal_crypto_store_unload_key(key_slot + 1);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        printf("enclave_valid_keys  : %d\r\n", enclave_valid_keys);
 | 
					        printf("enclave_valid_keys      : %d\r\n", enclave_valid_keys);
 | 
				
			||||||
        printf(
 | 
					        printf(
 | 
				
			||||||
            "enclave_valid       : %s\r\n",
 | 
					            "enclave_valid           : %s\r\n",
 | 
				
			||||||
            (enclave_valid_keys == ENCLAVE_SIGNATURE_KEY_SLOTS) ? "true" : "false");
 | 
					            (enclave_valid_keys == ENCLAVE_SIGNATURE_KEY_SLOTS) ? "true" : "false");
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        printf("radio_alive         : false\r\n");
 | 
					        printf("radio_alive             : false\r\n");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -41,6 +41,7 @@ Desktop* desktop_alloc() {
 | 
				
			|||||||
    desktop->debug_view = desktop_debug_alloc();
 | 
					    desktop->debug_view = desktop_debug_alloc();
 | 
				
			||||||
    desktop->first_start_view = desktop_first_start_alloc();
 | 
					    desktop->first_start_view = desktop_first_start_alloc();
 | 
				
			||||||
    desktop->hw_mismatch_popup = popup_alloc();
 | 
					    desktop->hw_mismatch_popup = popup_alloc();
 | 
				
			||||||
 | 
					    desktop->code_input = code_input_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    view_dispatcher_add_view(
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
        desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view));
 | 
					        desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view));
 | 
				
			||||||
@ -62,7 +63,8 @@ Desktop* desktop_alloc() {
 | 
				
			|||||||
        desktop->view_dispatcher,
 | 
					        desktop->view_dispatcher,
 | 
				
			||||||
        DesktopViewHwMismatch,
 | 
					        DesktopViewHwMismatch,
 | 
				
			||||||
        popup_get_view(desktop->hw_mismatch_popup));
 | 
					        popup_get_view(desktop->hw_mismatch_popup));
 | 
				
			||||||
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
					        desktop->view_dispatcher, DesktopViewPinSetup, code_input_get_view(desktop->code_input));
 | 
				
			||||||
    // Lock icon
 | 
					    // Lock icon
 | 
				
			||||||
    desktop->lock_viewport = view_port_alloc();
 | 
					    desktop->lock_viewport = view_port_alloc();
 | 
				
			||||||
    view_port_set_width(desktop->lock_viewport, icon_get_width(&I_Lock_8x8));
 | 
					    view_port_set_width(desktop->lock_viewport, icon_get_width(&I_Lock_8x8));
 | 
				
			||||||
@ -82,6 +84,7 @@ void desktop_free(Desktop* desktop) {
 | 
				
			|||||||
    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewDebug);
 | 
					    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewDebug);
 | 
				
			||||||
    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewFirstStart);
 | 
					    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewFirstStart);
 | 
				
			||||||
    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewHwMismatch);
 | 
					    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewHwMismatch);
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewPinSetup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    view_dispatcher_free(desktop->view_dispatcher);
 | 
					    view_dispatcher_free(desktop->view_dispatcher);
 | 
				
			||||||
    scene_manager_free(desktop->scene_manager);
 | 
					    scene_manager_free(desktop->scene_manager);
 | 
				
			||||||
@ -92,6 +95,7 @@ void desktop_free(Desktop* desktop) {
 | 
				
			|||||||
    desktop_debug_free(desktop->debug_view);
 | 
					    desktop_debug_free(desktop->debug_view);
 | 
				
			||||||
    desktop_first_start_free(desktop->first_start_view);
 | 
					    desktop_first_start_free(desktop->first_start_view);
 | 
				
			||||||
    popup_free(desktop->hw_mismatch_popup);
 | 
					    popup_free(desktop->hw_mismatch_popup);
 | 
				
			||||||
 | 
					    code_input_free(desktop->code_input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    furi_record_close("gui");
 | 
					    furi_record_close("gui");
 | 
				
			||||||
    desktop->gui = NULL;
 | 
					    desktop->gui = NULL;
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@
 | 
				
			|||||||
#include <gui/gui.h>
 | 
					#include <gui/gui.h>
 | 
				
			||||||
#include <gui/view_dispatcher.h>
 | 
					#include <gui/view_dispatcher.h>
 | 
				
			||||||
#include <gui/modules/popup.h>
 | 
					#include <gui/modules/popup.h>
 | 
				
			||||||
 | 
					#include <gui/modules/code_input.h>
 | 
				
			||||||
#include <gui/scene_manager.h>
 | 
					#include <gui/scene_manager.h>
 | 
				
			||||||
#include <assets_icons.h>
 | 
					#include <assets_icons.h>
 | 
				
			||||||
#include <storage/storage.h>
 | 
					#include <storage/storage.h>
 | 
				
			||||||
@ -29,6 +30,7 @@ typedef enum {
 | 
				
			|||||||
    DesktopViewDebug,
 | 
					    DesktopViewDebug,
 | 
				
			||||||
    DesktopViewFirstStart,
 | 
					    DesktopViewFirstStart,
 | 
				
			||||||
    DesktopViewHwMismatch,
 | 
					    DesktopViewHwMismatch,
 | 
				
			||||||
 | 
					    DesktopViewPinSetup,
 | 
				
			||||||
    DesktopViewTotal,
 | 
					    DesktopViewTotal,
 | 
				
			||||||
} DesktopViewEnum;
 | 
					} DesktopViewEnum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -46,7 +48,10 @@ struct Desktop {
 | 
				
			|||||||
    DesktopLockMenuView* lock_menu;
 | 
					    DesktopLockMenuView* lock_menu;
 | 
				
			||||||
    DesktopLockedView* locked_view;
 | 
					    DesktopLockedView* locked_view;
 | 
				
			||||||
    DesktopDebugView* debug_view;
 | 
					    DesktopDebugView* debug_view;
 | 
				
			||||||
 | 
					    CodeInput* code_input;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    DesktopSettings settings;
 | 
					    DesktopSettings settings;
 | 
				
			||||||
 | 
					    PinCode pincode_buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ViewPort* lock_viewport;
 | 
					    ViewPort* lock_viewport;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -3,11 +3,20 @@
 | 
				
			|||||||
#include <stdint.h>
 | 
					#include <stdint.h>
 | 
				
			||||||
#include <stdbool.h>
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define DESKTOP_SETTINGS_VER (0)
 | 
					#define DESKTOP_SETTINGS_VER (1)
 | 
				
			||||||
 | 
					#define PIN_MAX_LENGTH 12
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    uint8_t length;
 | 
				
			||||||
 | 
					    uint8_t data[PIN_MAX_LENGTH];
 | 
				
			||||||
 | 
					} PinCode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct {
 | 
					typedef struct {
 | 
				
			||||||
    uint8_t version;
 | 
					    uint8_t version;
 | 
				
			||||||
    uint16_t favorite;
 | 
					    uint16_t favorite;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    PinCode pincode;
 | 
				
			||||||
 | 
					    bool locked;
 | 
				
			||||||
} DesktopSettings;
 | 
					} DesktopSettings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool desktop_settings_load(DesktopSettings* desktop_settings);
 | 
					bool desktop_settings_load(DesktopSettings* desktop_settings);
 | 
				
			||||||
 | 
				
			|||||||
@ -33,10 +33,13 @@ DesktopSettingsApp* desktop_settings_app_alloc() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    app->submenu = submenu_alloc();
 | 
					    app->submenu = submenu_alloc();
 | 
				
			||||||
    view_dispatcher_add_view(
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
        app->view_dispatcher, DesktopSettingsAppViewMain, submenu_get_view(app->submenu));
 | 
					        app->view_dispatcher, DesktopSettingsAppViewMenu, submenu_get_view(app->submenu));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    app->code_input = code_input_alloc();
 | 
				
			||||||
    view_dispatcher_add_view(
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
        app->view_dispatcher, DesktopSettingsAppViewFavorite, submenu_get_view(app->submenu));
 | 
					        app->view_dispatcher,
 | 
				
			||||||
 | 
					        DesktopSettingsAppViewPincodeInput,
 | 
				
			||||||
 | 
					        code_input_get_view(app->code_input));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart);
 | 
					    scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart);
 | 
				
			||||||
    return app;
 | 
					    return app;
 | 
				
			||||||
@ -45,9 +48,10 @@ DesktopSettingsApp* desktop_settings_app_alloc() {
 | 
				
			|||||||
void desktop_settings_app_free(DesktopSettingsApp* app) {
 | 
					void desktop_settings_app_free(DesktopSettingsApp* app) {
 | 
				
			||||||
    furi_assert(app);
 | 
					    furi_assert(app);
 | 
				
			||||||
    // Variable item list
 | 
					    // Variable item list
 | 
				
			||||||
    view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMain);
 | 
					    view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
 | 
				
			||||||
    view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewFavorite);
 | 
					 | 
				
			||||||
    submenu_free(app->submenu);
 | 
					    submenu_free(app->submenu);
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewPincodeInput);
 | 
				
			||||||
 | 
					    code_input_free(app->code_input);
 | 
				
			||||||
    // View dispatcher
 | 
					    // View dispatcher
 | 
				
			||||||
    view_dispatcher_free(app->view_dispatcher);
 | 
					    view_dispatcher_free(app->view_dispatcher);
 | 
				
			||||||
    scene_manager_free(app->scene_manager);
 | 
					    scene_manager_free(app->scene_manager);
 | 
				
			||||||
 | 
				
			|||||||
@ -6,20 +6,32 @@
 | 
				
			|||||||
#include <gui/view_dispatcher.h>
 | 
					#include <gui/view_dispatcher.h>
 | 
				
			||||||
#include <gui/scene_manager.h>
 | 
					#include <gui/scene_manager.h>
 | 
				
			||||||
#include <gui/modules/submenu.h>
 | 
					#include <gui/modules/submenu.h>
 | 
				
			||||||
 | 
					#include <gui/modules/code_input.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "desktop_settings.h"
 | 
					#include "desktop_settings.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "scenes/desktop_settings_scene.h"
 | 
					#include "scenes/desktop_settings_scene.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef enum {
 | 
					typedef enum {
 | 
				
			||||||
    DesktopSettingsAppViewMain,
 | 
					    CodeEventsSetPin,
 | 
				
			||||||
    DesktopSettingsAppViewFavorite,
 | 
					    CodeEventsChangePin,
 | 
				
			||||||
 | 
					    CodeEventsDisablePin,
 | 
				
			||||||
 | 
					} CodeEventsEnum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    DesktopSettingsAppViewMenu,
 | 
				
			||||||
 | 
					    DesktopSettingsAppViewPincodeInput,
 | 
				
			||||||
} DesktopSettingsAppView;
 | 
					} DesktopSettingsAppView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct {
 | 
					typedef struct {
 | 
				
			||||||
    DesktopSettings settings;
 | 
					    DesktopSettings settings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Gui* gui;
 | 
					    Gui* gui;
 | 
				
			||||||
    SceneManager* scene_manager;
 | 
					    SceneManager* scene_manager;
 | 
				
			||||||
    ViewDispatcher* view_dispatcher;
 | 
					    ViewDispatcher* view_dispatcher;
 | 
				
			||||||
    Submenu* submenu;
 | 
					    Submenu* submenu;
 | 
				
			||||||
 | 
					    CodeInput* code_input;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t menu_idx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} DesktopSettingsApp;
 | 
					} DesktopSettingsApp;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,2 +1,4 @@
 | 
				
			|||||||
ADD_SCENE(desktop_settings, start, Start)
 | 
					ADD_SCENE(desktop_settings, start, Start)
 | 
				
			||||||
ADD_SCENE(desktop_settings, favorite, Favorite)
 | 
					ADD_SCENE(desktop_settings, favorite, Favorite)
 | 
				
			||||||
 | 
					ADD_SCENE(desktop_settings, pincode_menu, PinCodeMenu)
 | 
				
			||||||
 | 
					ADD_SCENE(desktop_settings, pincode_input, PinCodeInput)
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@ void desktop_settings_scene_favorite_on_enter(void* context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    submenu_set_header(app->submenu, "Quick access app:");
 | 
					    submenu_set_header(app->submenu, "Quick access app:");
 | 
				
			||||||
    submenu_set_selected_item(app->submenu, app->settings.favorite);
 | 
					    submenu_set_selected_item(app->submenu, app->settings.favorite);
 | 
				
			||||||
    view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewFavorite);
 | 
					    view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent event) {
 | 
					bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					#include "../desktop_settings_app.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define SCENE_EXIT_EVENT (0U)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void desktop_settings_scene_ok_callback(void* context) {
 | 
				
			||||||
 | 
					    DesktopSettingsApp* app = context;
 | 
				
			||||||
 | 
					    uint32_t state =
 | 
				
			||||||
 | 
					        scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinCodeInput);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(state == CodeEventsDisablePin) {
 | 
				
			||||||
 | 
					        memset(app->settings.pincode.data, 0, app->settings.pincode.length * sizeof(uint8_t));
 | 
				
			||||||
 | 
					        app->settings.pincode.length = 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void desktop_settings_scene_pincode_input_on_enter(void* context) {
 | 
				
			||||||
 | 
					    DesktopSettingsApp* app = context;
 | 
				
			||||||
 | 
					    CodeInput* code_input = app->code_input;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint32_t state =
 | 
				
			||||||
 | 
					        scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinCodeInput);
 | 
				
			||||||
 | 
					    bool update = state != CodeEventsDisablePin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    code_input_set_header_text(code_input, "PIN Code Setup");
 | 
				
			||||||
 | 
					    code_input_set_result_callback(
 | 
				
			||||||
 | 
					        code_input,
 | 
				
			||||||
 | 
					        desktop_settings_scene_ok_callback,
 | 
				
			||||||
 | 
					        NULL,
 | 
				
			||||||
 | 
					        app,
 | 
				
			||||||
 | 
					        app->settings.pincode.data,
 | 
				
			||||||
 | 
					        &app->settings.pincode.length,
 | 
				
			||||||
 | 
					        update);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewPincodeInput);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool desktop_settings_scene_pincode_input_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    DesktopSettingsApp* app = context;
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        switch(event.event) {
 | 
				
			||||||
 | 
					        case SCENE_EXIT_EVENT:
 | 
				
			||||||
 | 
					            scene_manager_previous_scene(app->scene_manager);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void desktop_settings_scene_pincode_input_on_exit(void* context) {
 | 
				
			||||||
 | 
					    DesktopSettingsApp* app = context;
 | 
				
			||||||
 | 
					    code_input_set_result_callback(app->code_input, NULL, NULL, NULL, NULL, NULL, 0);
 | 
				
			||||||
 | 
					    code_input_set_header_text(app->code_input, "");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					#include "../desktop_settings_app.h"
 | 
				
			||||||
 | 
					#include "applications.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void desktop_settings_scene_pincode_menu_submenu_callback(void* context, uint32_t index) {
 | 
				
			||||||
 | 
					    DesktopSettingsApp* app = context;
 | 
				
			||||||
 | 
					    view_dispatcher_send_custom_event(app->view_dispatcher, index);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void desktop_settings_scene_pincode_menu_on_enter(void* context) {
 | 
				
			||||||
 | 
					    DesktopSettingsApp* app = context;
 | 
				
			||||||
 | 
					    Submenu* submenu = app->submenu;
 | 
				
			||||||
 | 
					    submenu_clean(submenu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(!app->settings.pincode.length) {
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Set Pin",
 | 
				
			||||||
 | 
					            CodeEventsSetPin,
 | 
				
			||||||
 | 
					            desktop_settings_scene_pincode_menu_submenu_callback,
 | 
				
			||||||
 | 
					            app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Change Pin",
 | 
				
			||||||
 | 
					            CodeEventsChangePin,
 | 
				
			||||||
 | 
					            desktop_settings_scene_pincode_menu_submenu_callback,
 | 
				
			||||||
 | 
					            app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Disable",
 | 
				
			||||||
 | 
					            CodeEventsDisablePin,
 | 
				
			||||||
 | 
					            desktop_settings_scene_pincode_menu_submenu_callback,
 | 
				
			||||||
 | 
					            app);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    submenu_set_header(app->submenu, "Pin code settings:");
 | 
				
			||||||
 | 
					    submenu_set_selected_item(app->submenu, app->menu_idx);
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool desktop_settings_scene_pincode_menu_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    DesktopSettingsApp* app = context;
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        switch(event.event) {
 | 
				
			||||||
 | 
					        case CodeEventsSetPin:
 | 
				
			||||||
 | 
					            scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event);
 | 
				
			||||||
 | 
					            scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case CodeEventsChangePin:
 | 
				
			||||||
 | 
					            scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event);
 | 
				
			||||||
 | 
					            scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case CodeEventsDisablePin:
 | 
				
			||||||
 | 
					            scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event);
 | 
				
			||||||
 | 
					            scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void desktop_settings_scene_pincode_menu_on_exit(void* context) {
 | 
				
			||||||
 | 
					    DesktopSettingsApp* app = context;
 | 
				
			||||||
 | 
					    submenu_clean(app->submenu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -29,7 +29,7 @@ void desktop_settings_scene_start_on_enter(void* context) {
 | 
				
			|||||||
        desktop_settings_scene_start_submenu_callback,
 | 
					        desktop_settings_scene_start_submenu_callback,
 | 
				
			||||||
        app);
 | 
					        app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMain);
 | 
					    view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent event) {
 | 
					bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
@ -39,7 +39,11 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even
 | 
				
			|||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        switch(event.event) {
 | 
					        switch(event.event) {
 | 
				
			||||||
        case DesktopSettingsStartSubmenuIndexFavorite:
 | 
					        case DesktopSettingsStartSubmenuIndexFavorite:
 | 
				
			||||||
            scene_manager_next_scene(app->scene_manager, DesktopSettingsAppViewFavorite);
 | 
					            scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case DesktopSettingsStartSubmenuIndexPinSetup:
 | 
				
			||||||
 | 
					            scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeMenu);
 | 
				
			||||||
            consumed = true;
 | 
					            consumed = true;
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -4,3 +4,4 @@ ADD_SCENE(desktop, locked, Locked)
 | 
				
			|||||||
ADD_SCENE(desktop, debug, Debug)
 | 
					ADD_SCENE(desktop, debug, Debug)
 | 
				
			||||||
ADD_SCENE(desktop, first_start, FirstStart)
 | 
					ADD_SCENE(desktop, first_start, FirstStart)
 | 
				
			||||||
ADD_SCENE(desktop, hw_mismatch, HwMismatch)
 | 
					ADD_SCENE(desktop, hw_mismatch, HwMismatch)
 | 
				
			||||||
 | 
					ADD_SCENE(desktop, pinsetup, PinSetup)
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,10 @@ void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context)
 | 
				
			|||||||
void desktop_scene_lock_menu_on_enter(void* context) {
 | 
					void desktop_scene_lock_menu_on_enter(void* context) {
 | 
				
			||||||
    Desktop* desktop = (Desktop*)context;
 | 
					    Desktop* desktop = (Desktop*)context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    desktop_settings_load(&desktop->settings);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop);
 | 
					    desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop);
 | 
				
			||||||
 | 
					    desktop_lock_menu_pin_set(desktop->lock_menu, desktop->settings.pincode.length > 0);
 | 
				
			||||||
    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLockMenu);
 | 
					    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLockMenu);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -20,10 +23,25 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        switch(event.event) {
 | 
					        switch(event.event) {
 | 
				
			||||||
        case DesktopLockMenuEventLock:
 | 
					        case DesktopLockMenuEventLock:
 | 
				
			||||||
 | 
					            scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                desktop->scene_manager, DesktopSceneLocked, DesktopLockedNoPin);
 | 
				
			||||||
            scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
 | 
					            scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
 | 
				
			||||||
            consumed = true;
 | 
					            consumed = true;
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
					        case DesktopLockMenuEventPinLock:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if(desktop->settings.pincode.length > 0) {
 | 
				
			||||||
 | 
					                desktop->settings.locked = true;
 | 
				
			||||||
 | 
					                desktop_settings_save(&desktop->settings);
 | 
				
			||||||
 | 
					                scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                    desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin);
 | 
				
			||||||
 | 
					                scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                scene_manager_next_scene(desktop->scene_manager, DesktopScenePinSetup);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
        case DesktopLockMenuEventExit:
 | 
					        case DesktopLockMenuEventExit:
 | 
				
			||||||
            scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
 | 
					            scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
 | 
				
			||||||
            consumed = true;
 | 
					            consumed = true;
 | 
				
			||||||
 | 
				
			|||||||
@ -15,12 +15,39 @@ void desktop_scene_locked_on_enter(void* context) {
 | 
				
			|||||||
    desktop_locked_update_hint_timeout(locked_view);
 | 
					    desktop_locked_update_hint_timeout(locked_view);
 | 
				
			||||||
    desktop_locked_set_dolphin_animation(locked_view);
 | 
					    desktop_locked_set_dolphin_animation(locked_view);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    desktop_locked_with_pin(desktop->locked_view, state == DesktopLockedWithPin);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    view_port_enabled_set(desktop->lock_viewport, true);
 | 
					    view_port_enabled_set(desktop->lock_viewport, true);
 | 
				
			||||||
    osTimerStart(locked_view->timer, 63);
 | 
					    osTimerStart(locked_view->timer, 63);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked);
 | 
					    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopMainEvent event) {
 | 
				
			||||||
 | 
					    bool match = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size_t length = desktop->pincode_buffer.length;
 | 
				
			||||||
 | 
					    length = code_input_push(desktop->pincode_buffer.data, length, event);
 | 
				
			||||||
 | 
					    desktop->pincode_buffer.length = length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match = code_input_compare(
 | 
				
			||||||
 | 
					        desktop->pincode_buffer.data,
 | 
				
			||||||
 | 
					        length,
 | 
				
			||||||
 | 
					        desktop->settings.pincode.data,
 | 
				
			||||||
 | 
					        desktop->settings.pincode.length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(match) {
 | 
				
			||||||
 | 
					        desktop->pincode_buffer.length = 0;
 | 
				
			||||||
 | 
					        desktop->settings.locked = false;
 | 
				
			||||||
 | 
					        desktop_settings_save(&desktop->settings);
 | 
				
			||||||
 | 
					        desktop_main_unlocked(desktop->main_view);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return match;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
 | 
					bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    Desktop* desktop = (Desktop*)context;
 | 
					    Desktop* desktop = (Desktop*)context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -36,7 +63,17 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
        case DesktopLockedEventUpdate:
 | 
					        case DesktopLockedEventUpdate:
 | 
				
			||||||
            desktop_locked_manage_redraw(desktop->locked_view);
 | 
					            desktop_locked_manage_redraw(desktop->locked_view);
 | 
				
			||||||
            consumed = true;
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case DesktopLockedEventInputReset:
 | 
				
			||||||
 | 
					            desktop->pincode_buffer.length = 0;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
        default:
 | 
					        default:
 | 
				
			||||||
 | 
					            if(desktop_scene_locked_check_pin(desktop, event.event)) {
 | 
				
			||||||
 | 
					                scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                    desktop->scene_manager, DesktopSceneMain, DesktopMainEventUnlocked);
 | 
				
			||||||
 | 
					                scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
 | 
				
			||||||
 | 
					                consumed = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -34,6 +34,8 @@ void desktop_scene_main_on_enter(void* context) {
 | 
				
			|||||||
    desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop);
 | 
					    desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop);
 | 
				
			||||||
    view_port_enabled_set(desktop->lock_viewport, false);
 | 
					    view_port_enabled_set(desktop->lock_viewport, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    desktop_settings_load(&desktop->settings);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain) ==
 | 
					    if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain) ==
 | 
				
			||||||
       DesktopMainEventUnlocked) {
 | 
					       DesktopMainEventUnlocked) {
 | 
				
			||||||
        desktop_main_unlocked(desktop->main_view);
 | 
					        desktop_main_unlocked(desktop->main_view);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										50
									
								
								applications/desktop/scenes/desktop_scene_pinsetup.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								applications/desktop/scenes/desktop_scene_pinsetup.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					#include "../desktop_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define SCENE_EXIT_EVENT (0U)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void desktop_scene_ok_callback(void* context) {
 | 
				
			||||||
 | 
					    Desktop* app = context;
 | 
				
			||||||
 | 
					    desktop_settings_save(&app->settings);
 | 
				
			||||||
 | 
					    view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void desktop_scene_pinsetup_on_enter(void* context) {
 | 
				
			||||||
 | 
					    Desktop* app = context;
 | 
				
			||||||
 | 
					    CodeInput* code_input = app->code_input;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    code_input_set_result_callback(
 | 
				
			||||||
 | 
					        code_input,
 | 
				
			||||||
 | 
					        desktop_scene_ok_callback,
 | 
				
			||||||
 | 
					        NULL,
 | 
				
			||||||
 | 
					        app,
 | 
				
			||||||
 | 
					        app->settings.pincode.data,
 | 
				
			||||||
 | 
					        &app->settings.pincode.length,
 | 
				
			||||||
 | 
					        true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(app->view_dispatcher, DesktopViewPinSetup);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool desktop_scene_pinsetup_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    Desktop* app = context;
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        switch(event.event) {
 | 
				
			||||||
 | 
					        case SCENE_EXIT_EVENT:
 | 
				
			||||||
 | 
					            scene_manager_previous_scene(app->scene_manager);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void desktop_scene_pinsetup_on_exit(void* context) {
 | 
				
			||||||
 | 
					    Desktop* app = context;
 | 
				
			||||||
 | 
					    code_input_set_result_callback(app->code_input, NULL, NULL, NULL, NULL, NULL, 0);
 | 
				
			||||||
 | 
					    code_input_set_header_text(app->code_input, "");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -42,7 +42,7 @@ void desktop_debug_render(Canvas* canvas, void* model) {
 | 
				
			|||||||
            my_name ? my_name : "Unknown");
 | 
					            my_name ? my_name : "Unknown");
 | 
				
			||||||
        canvas_draw_str(canvas, 5, 23, buffer);
 | 
					        canvas_draw_str(canvas, 5, 23, buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ver = m->screen == DesktopViewStatsBoot ? furi_hal_version_get_boot_version() :
 | 
					        ver = m->screen == DesktopViewStatsBoot ? furi_hal_version_get_bootloader_version() :
 | 
				
			||||||
                                                  furi_hal_version_get_firmware_version();
 | 
					                                                  furi_hal_version_get_firmware_version();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(!ver) {
 | 
					        if(!ver) {
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,14 @@ void desktop_lock_menu_set_callback(
 | 
				
			|||||||
    lock_menu->context = context;
 | 
					    lock_menu->context = context;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set) {
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        lock_menu->view, (DesktopLockMenuViewModel * model) {
 | 
				
			||||||
 | 
					            model->pin_set = pin_is_set;
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu) {
 | 
					void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu) {
 | 
				
			||||||
    with_view_model(
 | 
					    with_view_model(
 | 
				
			||||||
        lock_menu->view, (DesktopLockMenuViewModel * model) {
 | 
					        lock_menu->view, (DesktopLockMenuViewModel * model) {
 | 
				
			||||||
@ -26,6 +34,10 @@ static void lock_menu_callback(void* context, uint8_t index) {
 | 
				
			|||||||
    switch(index) {
 | 
					    switch(index) {
 | 
				
			||||||
    case 0: // lock
 | 
					    case 0: // lock
 | 
				
			||||||
        lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context);
 | 
					        lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    case 1: // lock
 | 
				
			||||||
 | 
					        lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
    default: // wip message
 | 
					    default: // wip message
 | 
				
			||||||
        with_view_model(
 | 
					        with_view_model(
 | 
				
			||||||
            lock_menu->view, (DesktopLockMenuViewModel * model) {
 | 
					            lock_menu->view, (DesktopLockMenuViewModel * model) {
 | 
				
			||||||
@ -37,7 +49,7 @@ static void lock_menu_callback(void* context, uint8_t index) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void desktop_lock_menu_render(Canvas* canvas, void* model) {
 | 
					void desktop_lock_menu_render(Canvas* canvas, void* model) {
 | 
				
			||||||
    const char* Lockmenu_Items[3] = {"Lock", "Set PIN", "DUMB mode"};
 | 
					    const char* Lockmenu_Items[3] = {"Lock", "Lock with PIN", "DUMB mode"};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    DesktopLockMenuViewModel* m = model;
 | 
					    DesktopLockMenuViewModel* m = model;
 | 
				
			||||||
    canvas_clear(canvas);
 | 
					    canvas_clear(canvas);
 | 
				
			||||||
@ -47,13 +59,13 @@ void desktop_lock_menu_render(Canvas* canvas, void* model) {
 | 
				
			|||||||
    canvas_set_font(canvas, FontSecondary);
 | 
					    canvas_set_font(canvas, FontSecondary);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for(uint8_t i = 0; i < 3; ++i) {
 | 
					    for(uint8_t i = 0; i < 3; ++i) {
 | 
				
			||||||
        canvas_draw_str_aligned(
 | 
					        const char* str = Lockmenu_Items[i];
 | 
				
			||||||
            canvas,
 | 
					
 | 
				
			||||||
            64,
 | 
					        if(i == 1 && !m->pin_set) str = "Set PIN";
 | 
				
			||||||
            13 + (i * 17),
 | 
					        if(m->hint_timeout && m->idx == 2 && m->idx == i) str = "Not implemented";
 | 
				
			||||||
            AlignCenter,
 | 
					
 | 
				
			||||||
            AlignCenter,
 | 
					        canvas_draw_str_aligned(canvas, 64, 13 + (i * 17), AlignCenter, AlignCenter, str);
 | 
				
			||||||
            (m->hint_timeout && m->idx == i && m->idx) ? "Not implemented" : Lockmenu_Items[i]);
 | 
					
 | 
				
			||||||
        if(m->idx == i) elements_frame(canvas, 15, 5 + (i * 17), 98, 15);
 | 
					        if(m->idx == i) elements_frame(canvas, 15, 5 + (i * 17), 98, 15);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@
 | 
				
			|||||||
typedef enum {
 | 
					typedef enum {
 | 
				
			||||||
    DesktopLockMenuEventLock,
 | 
					    DesktopLockMenuEventLock,
 | 
				
			||||||
    DesktopLockMenuEventUnlock,
 | 
					    DesktopLockMenuEventUnlock,
 | 
				
			||||||
 | 
					    DesktopLockMenuEventPinLock,
 | 
				
			||||||
    DesktopLockMenuEventExit,
 | 
					    DesktopLockMenuEventExit,
 | 
				
			||||||
} DesktopLockMenuEvent;
 | 
					} DesktopLockMenuEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -27,6 +28,7 @@ struct DesktopLockMenuView {
 | 
				
			|||||||
typedef struct {
 | 
					typedef struct {
 | 
				
			||||||
    uint8_t idx;
 | 
					    uint8_t idx;
 | 
				
			||||||
    uint8_t hint_timeout;
 | 
					    uint8_t hint_timeout;
 | 
				
			||||||
 | 
					    bool pin_set;
 | 
				
			||||||
} DesktopLockMenuViewModel;
 | 
					} DesktopLockMenuViewModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void desktop_lock_menu_set_callback(
 | 
					void desktop_lock_menu_set_callback(
 | 
				
			||||||
@ -35,6 +37,7 @@ void desktop_lock_menu_set_callback(
 | 
				
			|||||||
    void* context);
 | 
					    void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu);
 | 
					View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu);
 | 
				
			||||||
 | 
					void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set);
 | 
				
			||||||
void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu);
 | 
					void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu);
 | 
				
			||||||
DesktopLockMenuView* desktop_lock_menu_alloc();
 | 
					DesktopLockMenuView* desktop_lock_menu_alloc();
 | 
				
			||||||
void desktop_lock_menu_free(DesktopLockMenuView* lock_menu);
 | 
					void desktop_lock_menu_free(DesktopLockMenuView* lock_menu);
 | 
				
			||||||
 | 
				
			|||||||
@ -80,6 +80,14 @@ void desktop_locked_reset_counter(DesktopLockedView* locked_view) {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void desktop_locked_with_pin(DesktopLockedView* locked_view, bool locked) {
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        locked_view->view, (DesktopLockedViewModel * model) {
 | 
				
			||||||
 | 
					            model->pin_lock = locked;
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void desktop_locked_render(Canvas* canvas, void* model) {
 | 
					void desktop_locked_render(Canvas* canvas, void* model) {
 | 
				
			||||||
    DesktopLockedViewModel* m = model;
 | 
					    DesktopLockedViewModel* m = model;
 | 
				
			||||||
    uint32_t now = osKernelGetTickCount();
 | 
					    uint32_t now = osKernelGetTickCount();
 | 
				
			||||||
@ -100,7 +108,7 @@ void desktop_locked_render(Canvas* canvas, void* model) {
 | 
				
			|||||||
            canvas_set_font(canvas, FontPrimary);
 | 
					            canvas_set_font(canvas, FontPrimary);
 | 
				
			||||||
            elements_multiline_text_framed(canvas, 42, 30, "Locked");
 | 
					            elements_multiline_text_framed(canvas, 42, 30, "Locked");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        } else {
 | 
					        } else if(!m->pin_lock) {
 | 
				
			||||||
            canvas_set_font(canvas, FontSecondary);
 | 
					            canvas_set_font(canvas, FontSecondary);
 | 
				
			||||||
            canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49);
 | 
					            canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49);
 | 
				
			||||||
            elements_multiline_text(canvas, 65, 20, "To unlock\npress:");
 | 
					            elements_multiline_text(canvas, 65, 20, "To unlock\npress:");
 | 
				
			||||||
@ -116,27 +124,49 @@ View* desktop_locked_get_view(DesktopLockedView* locked_view) {
 | 
				
			|||||||
bool desktop_locked_input(InputEvent* event, void* context) {
 | 
					bool desktop_locked_input(InputEvent* event, void* context) {
 | 
				
			||||||
    furi_assert(event);
 | 
					    furi_assert(event);
 | 
				
			||||||
    furi_assert(context);
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    DesktopLockedView* locked_view = context;
 | 
					    DesktopLockedView* locked_view = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint32_t press_time = 0;
 | 
				
			||||||
 | 
					    bool locked_with_pin = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        locked_view->view, (DesktopLockedViewModel * model) {
 | 
				
			||||||
 | 
					            locked_with_pin = model->pin_lock;
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(event->type == InputTypeShort) {
 | 
					    if(event->type == InputTypeShort) {
 | 
				
			||||||
        desktop_locked_update_hint_timeout(locked_view);
 | 
					        if(locked_with_pin) {
 | 
				
			||||||
 | 
					            press_time = osKernelGetTickCount();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(event->key == InputKeyBack) {
 | 
					            if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT * 3) {
 | 
				
			||||||
            uint32_t press_time = osKernelGetTickCount();
 | 
					 | 
				
			||||||
            // check if pressed sequentially
 | 
					 | 
				
			||||||
            if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) {
 | 
					 | 
				
			||||||
                locked_view->lock_lastpress = press_time;
 | 
					                locked_view->lock_lastpress = press_time;
 | 
				
			||||||
                locked_view->lock_count = 0;
 | 
					                locked_view->callback(DesktopLockedEventInputReset, locked_view->context);
 | 
				
			||||||
            } else if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) {
 | 
					 | 
				
			||||||
                locked_view->lock_lastpress = press_time;
 | 
					 | 
				
			||||||
                locked_view->lock_count++;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if(locked_view->lock_count == UNLOCK_CNT) {
 | 
					            locked_view->callback(event->key, locked_view->context);
 | 
				
			||||||
                locked_view->lock_count = 0;
 | 
					        } else {
 | 
				
			||||||
                locked_view->callback(DesktopLockedEventUnlock, locked_view->context);
 | 
					            desktop_locked_update_hint_timeout(locked_view);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if(event->key == InputKeyBack) {
 | 
				
			||||||
 | 
					                press_time = osKernelGetTickCount();
 | 
				
			||||||
 | 
					                // check if pressed sequentially
 | 
				
			||||||
 | 
					                if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) {
 | 
				
			||||||
 | 
					                    locked_view->lock_lastpress = press_time;
 | 
				
			||||||
 | 
					                    locked_view->lock_count++;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if(locked_view->lock_count == UNLOCK_CNT) {
 | 
				
			||||||
 | 
					                    locked_view->lock_count = 0;
 | 
				
			||||||
 | 
					                    locked_view->callback(DesktopLockedEventUnlock, locked_view->context);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) {
 | 
				
			||||||
 | 
					            locked_view->lock_lastpress = press_time;
 | 
				
			||||||
 | 
					            locked_view->lock_count = 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // All events consumed
 | 
					    // All events consumed
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
 | 
				
			|||||||
@ -15,10 +15,16 @@
 | 
				
			|||||||
#define DOOR_R_POS_MIN 60
 | 
					#define DOOR_R_POS_MIN 60
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef enum {
 | 
					typedef enum {
 | 
				
			||||||
    DesktopLockedEventUnlock,
 | 
					    DesktopLockedEventUnlock = 10U,
 | 
				
			||||||
    DesktopLockedEventUpdate,
 | 
					    DesktopLockedEventUpdate = 11U,
 | 
				
			||||||
 | 
					    DesktopLockedEventInputReset = 12U,
 | 
				
			||||||
} DesktopLockedEvent;
 | 
					} DesktopLockedEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    DesktopLockedWithPin,
 | 
				
			||||||
 | 
					    DesktopLockedNoPin,
 | 
				
			||||||
 | 
					} DesktopLockedSceneState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct DesktopLockedView DesktopLockedView;
 | 
					typedef struct DesktopLockedView DesktopLockedView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context);
 | 
					typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context);
 | 
				
			||||||
@ -42,6 +48,7 @@ typedef struct {
 | 
				
			|||||||
    int8_t door_right_x;
 | 
					    int8_t door_right_x;
 | 
				
			||||||
    bool animation_seq_end;
 | 
					    bool animation_seq_end;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool pin_lock;
 | 
				
			||||||
} DesktopLockedViewModel;
 | 
					} DesktopLockedViewModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void desktop_locked_set_callback(
 | 
					void desktop_locked_set_callback(
 | 
				
			||||||
@ -58,5 +65,4 @@ void desktop_locked_manage_redraw(DesktopLockedView* locked_view);
 | 
				
			|||||||
View* desktop_locked_get_view(DesktopLockedView* locked_view);
 | 
					View* desktop_locked_get_view(DesktopLockedView* locked_view);
 | 
				
			||||||
DesktopLockedView* desktop_locked_alloc();
 | 
					DesktopLockedView* desktop_locked_alloc();
 | 
				
			||||||
void desktop_locked_free(DesktopLockedView* locked_view);
 | 
					void desktop_locked_free(DesktopLockedView* locked_view);
 | 
				
			||||||
void desktop_main_unlocked(DesktopMainView* main_view);
 | 
					void desktop_locked_with_pin(DesktopLockedView* lock_menu, bool locked);
 | 
				
			||||||
void desktop_main_reset_hint(DesktopMainView* main_view);
 | 
					 | 
				
			||||||
@ -67,6 +67,7 @@ bool desktop_main_input(InputEvent* event, void* context) {
 | 
				
			|||||||
    } else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
 | 
					    } else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
 | 
				
			||||||
        main_view->callback(DesktopMainEventOpenFavorite, main_view->context);
 | 
					        main_view->callback(DesktopMainEventOpenFavorite, main_view->context);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    desktop_main_reset_hint(main_view);
 | 
					    desktop_main_reset_hint(main_view);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
 | 
				
			|||||||
@ -7,12 +7,12 @@
 | 
				
			|||||||
#include <furi.h>
 | 
					#include <furi.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef enum {
 | 
					typedef enum {
 | 
				
			||||||
    DesktopMainEventOpenMenu,
 | 
					 | 
				
			||||||
    DesktopMainEventOpenLockMenu,
 | 
					    DesktopMainEventOpenLockMenu,
 | 
				
			||||||
    DesktopMainEventOpenDebug,
 | 
					 | 
				
			||||||
    DesktopMainEventUnlocked,
 | 
					 | 
				
			||||||
    DesktopMainEventOpenArchive,
 | 
					    DesktopMainEventOpenArchive,
 | 
				
			||||||
    DesktopMainEventOpenFavorite,
 | 
					    DesktopMainEventOpenFavorite,
 | 
				
			||||||
 | 
					    DesktopMainEventOpenMenu,
 | 
				
			||||||
 | 
					    DesktopMainEventOpenDebug,
 | 
				
			||||||
 | 
					    DesktopMainEventUnlocked,
 | 
				
			||||||
} DesktopMainEvent;
 | 
					} DesktopMainEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct DesktopMainView DesktopMainView;
 | 
					typedef struct DesktopMainView DesktopMainView;
 | 
				
			||||||
@ -37,9 +37,8 @@ void desktop_main_set_callback(
 | 
				
			|||||||
    void* context);
 | 
					    void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
View* desktop_main_get_view(DesktopMainView* main_view);
 | 
					View* desktop_main_get_view(DesktopMainView* main_view);
 | 
				
			||||||
 | 
					 | 
				
			||||||
DesktopMainView* desktop_main_alloc();
 | 
					DesktopMainView* desktop_main_alloc();
 | 
				
			||||||
 | 
					 | 
				
			||||||
void desktop_main_free(DesktopMainView* main_view);
 | 
					void desktop_main_free(DesktopMainView* main_view);
 | 
				
			||||||
 | 
					 | 
				
			||||||
void desktop_main_switch_dolphin_animation(DesktopMainView* main_view);
 | 
					void desktop_main_switch_dolphin_animation(DesktopMainView* main_view);
 | 
				
			||||||
 | 
					void desktop_main_unlocked(DesktopMainView* main_view);
 | 
				
			||||||
 | 
					void desktop_main_reset_hint(DesktopMainView* main_view);
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,14 @@
 | 
				
			|||||||
#include <gui/modules/variable-item-list.h>
 | 
					#include <gui/modules/variable-item-list.h>
 | 
				
			||||||
#include "views/gpio_test.h"
 | 
					#include "views/gpio_test.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF (0UL)
 | 
				
			||||||
 | 
					#define GPIO_SCENE_START_CUSTOM_EVENT_OTG_ON (1UL)
 | 
				
			||||||
 | 
					#define GPIO_SCENE_START_CUSTOM_EVENT_TEST (2UL)
 | 
				
			||||||
 | 
					#define GPIO_SCENE_START_CUSTOM_EVENT_USB_UART (3UL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define GPIO_SCENE_USB_UART_CUSTOM_EVENT_ENABLE (4UL)
 | 
				
			||||||
 | 
					#define GPIO_SCENE_USB_UART_CUSTOM_EVENT_DISABLE (5UL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct GpioApp {
 | 
					struct GpioApp {
 | 
				
			||||||
    Gui* gui;
 | 
					    Gui* gui;
 | 
				
			||||||
    ViewDispatcher* view_dispatcher;
 | 
					    ViewDispatcher* view_dispatcher;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,6 @@
 | 
				
			|||||||
#include "../gpio_app_i.h"
 | 
					#include "../gpio_app_i.h"
 | 
				
			||||||
#include "furi-hal-power.h"
 | 
					#include "furi-hal-power.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF (0UL)
 | 
					 | 
				
			||||||
#define GPIO_SCENE_START_CUSTOM_EVENT_OTG_ON (1UL)
 | 
					 | 
				
			||||||
#define GPIO_SCENE_START_CUSTOM_EVENT_TEST (2UL)
 | 
					 | 
				
			||||||
#define GPIO_SCENE_START_CUSTOM_EVENT_USB_UART (3UL)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum GpioItem {
 | 
					enum GpioItem {
 | 
				
			||||||
    GpioItemOtg,
 | 
					    GpioItemOtg,
 | 
				
			||||||
    GpioItemTest,
 | 
					    GpioItemTest,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +1,6 @@
 | 
				
			|||||||
 | 
					#include "../usb_uart_bridge.h"
 | 
				
			||||||
#include "../gpio_app_i.h"
 | 
					#include "../gpio_app_i.h"
 | 
				
			||||||
#include "furi-hal.h"
 | 
					#include "furi-hal.h"
 | 
				
			||||||
#include <stream_buffer.h>
 | 
					 | 
				
			||||||
#include <furi-hal-usb-cdc_i.h>
 | 
					 | 
				
			||||||
#include "usb_cdc.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#define USB_PKT_LEN CDC_DATA_SZ
 | 
					 | 
				
			||||||
#define USB_UART_RX_BUF_SIZE (USB_PKT_LEN * 3)
 | 
					 | 
				
			||||||
#define USB_UART_TX_BUF_SIZE (USB_PKT_LEN * 3)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef enum {
 | 
					 | 
				
			||||||
    WorkerCmdStop = (1 << 0),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
} WorkerCommandFlags;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef enum {
 | 
					typedef enum {
 | 
				
			||||||
    UsbUartLineIndexVcp,
 | 
					    UsbUartLineIndexVcp,
 | 
				
			||||||
@ -21,42 +10,7 @@ typedef enum {
 | 
				
			|||||||
    UsbUartLineIndexDisable,
 | 
					    UsbUartLineIndexDisable,
 | 
				
			||||||
} LineIndex;
 | 
					} LineIndex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef enum {
 | 
					static UsbUartConfig* cfg_set;
 | 
				
			||||||
    UsbUartPortUSART1 = 0,
 | 
					 | 
				
			||||||
    UsbUartPortLPUART1 = 1,
 | 
					 | 
				
			||||||
} PortIdx;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef struct {
 | 
					 | 
				
			||||||
    uint8_t vcp_ch;
 | 
					 | 
				
			||||||
    PortIdx uart_ch;
 | 
					 | 
				
			||||||
    uint32_t baudrate;
 | 
					 | 
				
			||||||
} UsbUartConfig;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef struct {
 | 
					 | 
				
			||||||
    UsbUartConfig cfg_cur;
 | 
					 | 
				
			||||||
    UsbUartConfig cfg_set;
 | 
					 | 
				
			||||||
    char br_text[8];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool running;
 | 
					 | 
				
			||||||
    osThreadId_t parent_thread;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    osThreadAttr_t thread_attr;
 | 
					 | 
				
			||||||
    osThreadId_t thread;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    osThreadAttr_t tx_thread_attr;
 | 
					 | 
				
			||||||
    osThreadId_t tx_thread;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    StreamBufferHandle_t rx_stream;
 | 
					 | 
				
			||||||
    osSemaphoreId_t rx_done_sem;
 | 
					 | 
				
			||||||
    osSemaphoreId_t usb_sof_sem;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    StreamBufferHandle_t tx_stream;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    uint8_t rx_buf[USB_PKT_LEN];
 | 
					 | 
				
			||||||
    uint8_t tx_buf[USB_PKT_LEN];
 | 
					 | 
				
			||||||
} UsbUartParams;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static UsbUartParams* usb_uart;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char* vcp_ch[] = {"0 (CLI)", "1"};
 | 
					static const char* vcp_ch[] = {"0 (CLI)", "1"};
 | 
				
			||||||
static const char* uart_ch[] = {"USART1", "LPUART1"};
 | 
					static const char* uart_ch[] = {"USART1", "LPUART1"};
 | 
				
			||||||
@ -73,197 +27,14 @@ static const uint32_t baudrate_list[] = {
 | 
				
			|||||||
    921600,
 | 
					    921600,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void vcp_on_cdc_tx_complete();
 | 
					 | 
				
			||||||
static void vcp_on_cdc_rx();
 | 
					 | 
				
			||||||
static void vcp_state_callback(uint8_t state);
 | 
					 | 
				
			||||||
static void vcp_on_cdc_control_line(uint8_t state);
 | 
					 | 
				
			||||||
static void vcp_on_line_config(struct usb_cdc_line_coding* config);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static CdcCallbacks cdc_cb = {
 | 
					 | 
				
			||||||
    vcp_on_cdc_tx_complete,
 | 
					 | 
				
			||||||
    vcp_on_cdc_rx,
 | 
					 | 
				
			||||||
    vcp_state_callback,
 | 
					 | 
				
			||||||
    vcp_on_cdc_control_line,
 | 
					 | 
				
			||||||
    vcp_on_line_config,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* USB UART worker */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void usb_uart_tx_thread(void* context);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data) {
 | 
					 | 
				
			||||||
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(ev == UartIrqEventRXNE) {
 | 
					 | 
				
			||||||
        size_t ret =
 | 
					 | 
				
			||||||
            xStreamBufferSendFromISR(usb_uart->rx_stream, &data, 1, &xHigherPriorityTaskWoken);
 | 
					 | 
				
			||||||
        furi_check(ret == 1);
 | 
					 | 
				
			||||||
        ret = xStreamBufferBytesAvailable(usb_uart->rx_stream);
 | 
					 | 
				
			||||||
        if(ret > USB_PKT_LEN) osSemaphoreRelease(usb_uart->rx_done_sem);
 | 
					 | 
				
			||||||
    } else if(ev == UartIrqEventIDLE) {
 | 
					 | 
				
			||||||
        osSemaphoreRelease(usb_uart->rx_done_sem);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void usb_uart_worker(void* context) {
 | 
					 | 
				
			||||||
    memcpy(&usb_uart->cfg_cur, &usb_uart->cfg_set, sizeof(UsbUartConfig));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    usb_uart->rx_stream = xStreamBufferCreate(USB_UART_RX_BUF_SIZE, 1);
 | 
					 | 
				
			||||||
    usb_uart->rx_done_sem = osSemaphoreNew(1, 1, NULL);
 | 
					 | 
				
			||||||
    usb_uart->usb_sof_sem = osSemaphoreNew(1, 1, NULL);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    usb_uart->tx_stream = xStreamBufferCreate(USB_UART_TX_BUF_SIZE, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    usb_uart->tx_thread = NULL;
 | 
					 | 
				
			||||||
    usb_uart->tx_thread_attr.name = "usb_uart_tx";
 | 
					 | 
				
			||||||
    usb_uart->tx_thread_attr.stack_size = 512;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    UsbMode usb_mode_prev = furi_hal_usb_get_config();
 | 
					 | 
				
			||||||
    if(usb_uart->cfg_cur.vcp_ch == 0) {
 | 
					 | 
				
			||||||
        furi_hal_usb_set_config(UsbModeVcpSingle);
 | 
					 | 
				
			||||||
        furi_hal_vcp_disable();
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        furi_hal_usb_set_config(UsbModeVcpDual);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(usb_uart->cfg_cur.uart_ch == UsbUartPortUSART1) {
 | 
					 | 
				
			||||||
        furi_hal_usart_init();
 | 
					 | 
				
			||||||
        furi_hal_usart_set_irq_cb(usb_uart_on_irq_cb);
 | 
					 | 
				
			||||||
        if(usb_uart->cfg_cur.baudrate != 0)
 | 
					 | 
				
			||||||
            furi_hal_usart_set_br(usb_uart->cfg_cur.baudrate);
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
            vcp_on_line_config(furi_hal_cdc_get_port_settings(usb_uart->cfg_cur.vcp_ch));
 | 
					 | 
				
			||||||
    } else if(usb_uart->cfg_cur.uart_ch == UsbUartPortLPUART1) {
 | 
					 | 
				
			||||||
        furi_hal_lpuart_init();
 | 
					 | 
				
			||||||
        furi_hal_lpuart_set_irq_cb(usb_uart_on_irq_cb);
 | 
					 | 
				
			||||||
        if(usb_uart->cfg_cur.baudrate != 0)
 | 
					 | 
				
			||||||
            furi_hal_lpuart_set_br(usb_uart->cfg_cur.baudrate);
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
            vcp_on_line_config(furi_hal_cdc_get_port_settings(usb_uart->cfg_cur.vcp_ch));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    furi_hal_cdc_set_callbacks(usb_uart->cfg_cur.vcp_ch, &cdc_cb);
 | 
					 | 
				
			||||||
    usb_uart->tx_thread = osThreadNew(usb_uart_tx_thread, NULL, &usb_uart->tx_thread_attr);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    while(1) {
 | 
					 | 
				
			||||||
        furi_check(osSemaphoreAcquire(usb_uart->rx_done_sem, osWaitForever) == osOK);
 | 
					 | 
				
			||||||
        if(osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny, 0) == WorkerCmdStop) break;
 | 
					 | 
				
			||||||
        size_t len = 0;
 | 
					 | 
				
			||||||
        do {
 | 
					 | 
				
			||||||
            len = xStreamBufferReceive(usb_uart->rx_stream, usb_uart->rx_buf, USB_PKT_LEN, 0);
 | 
					 | 
				
			||||||
            if(len > 0) {
 | 
					 | 
				
			||||||
                if(osSemaphoreAcquire(usb_uart->usb_sof_sem, 100) == osOK)
 | 
					 | 
				
			||||||
                    furi_hal_cdc_send(usb_uart->cfg_cur.vcp_ch, usb_uart->rx_buf, len);
 | 
					 | 
				
			||||||
                else
 | 
					 | 
				
			||||||
                    xStreamBufferReset(usb_uart->rx_stream);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } while(len > 0);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    osThreadTerminate(usb_uart->tx_thread);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(usb_uart->cfg_cur.uart_ch == UsbUartPortUSART1)
 | 
					 | 
				
			||||||
        furi_hal_usart_deinit();
 | 
					 | 
				
			||||||
    else if(usb_uart->cfg_cur.uart_ch == UsbUartPortLPUART1)
 | 
					 | 
				
			||||||
        furi_hal_lpuart_deinit();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    furi_hal_cdc_set_callbacks(usb_uart->cfg_cur.vcp_ch, NULL);
 | 
					 | 
				
			||||||
    furi_hal_usb_set_config(usb_mode_prev);
 | 
					 | 
				
			||||||
    if(usb_uart->cfg_cur.vcp_ch == 0) furi_hal_vcp_enable();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    vStreamBufferDelete(usb_uart->rx_stream);
 | 
					 | 
				
			||||||
    osSemaphoreDelete(usb_uart->rx_done_sem);
 | 
					 | 
				
			||||||
    osSemaphoreDelete(usb_uart->usb_sof_sem);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    vStreamBufferDelete(usb_uart->tx_stream);
 | 
					 | 
				
			||||||
    osThreadFlagsSet(usb_uart->parent_thread, WorkerCmdStop);
 | 
					 | 
				
			||||||
    osThreadExit();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void usb_uart_tx_thread(void* context) {
 | 
					 | 
				
			||||||
    uint8_t data = 0;
 | 
					 | 
				
			||||||
    while(1) {
 | 
					 | 
				
			||||||
        size_t len = xStreamBufferReceive(usb_uart->tx_stream, &data, 1, osWaitForever);
 | 
					 | 
				
			||||||
        if(len > 0) {
 | 
					 | 
				
			||||||
            if(usb_uart->cfg_cur.uart_ch == UsbUartPortUSART1)
 | 
					 | 
				
			||||||
                furi_hal_usart_tx(&data, len);
 | 
					 | 
				
			||||||
            else if(usb_uart->cfg_cur.uart_ch == UsbUartPortLPUART1)
 | 
					 | 
				
			||||||
                furi_hal_lpuart_tx(&data, len);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    osThreadExit();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* VCP callbacks */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void vcp_on_cdc_tx_complete() {
 | 
					 | 
				
			||||||
    osSemaphoreRelease(usb_uart->usb_sof_sem);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void vcp_on_cdc_rx() {
 | 
					 | 
				
			||||||
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    uint16_t max_len = xStreamBufferSpacesAvailable(usb_uart->tx_stream);
 | 
					 | 
				
			||||||
    if(max_len > 0) {
 | 
					 | 
				
			||||||
        if(max_len > USB_PKT_LEN) max_len = USB_PKT_LEN;
 | 
					 | 
				
			||||||
        int32_t size = furi_hal_cdc_receive(usb_uart->cfg_cur.vcp_ch, usb_uart->tx_buf, max_len);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if(size > 0) {
 | 
					 | 
				
			||||||
            size_t ret = xStreamBufferSendFromISR(
 | 
					 | 
				
			||||||
                usb_uart->tx_stream, usb_uart->tx_buf, size, &xHigherPriorityTaskWoken);
 | 
					 | 
				
			||||||
            furi_check(ret == size);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void vcp_state_callback(uint8_t state) {
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void vcp_on_cdc_control_line(uint8_t state) {
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void vcp_on_line_config(struct usb_cdc_line_coding* config) {
 | 
					 | 
				
			||||||
    if((usb_uart->cfg_cur.baudrate == 0) && (config->dwDTERate != 0)) {
 | 
					 | 
				
			||||||
        if(usb_uart->cfg_cur.uart_ch == UsbUartPortUSART1)
 | 
					 | 
				
			||||||
            furi_hal_usart_set_br(config->dwDTERate);
 | 
					 | 
				
			||||||
        else if(usb_uart->cfg_cur.uart_ch == UsbUartPortLPUART1)
 | 
					 | 
				
			||||||
            furi_hal_lpuart_set_br(config->dwDTERate);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* USB UART app */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void usb_uart_enable() {
 | 
					 | 
				
			||||||
    if(usb_uart->running == false) {
 | 
					 | 
				
			||||||
        usb_uart->thread = NULL;
 | 
					 | 
				
			||||||
        usb_uart->thread_attr.name = "usb_uart";
 | 
					 | 
				
			||||||
        usb_uart->thread_attr.stack_size = 1024;
 | 
					 | 
				
			||||||
        usb_uart->parent_thread = osThreadGetId();
 | 
					 | 
				
			||||||
        usb_uart->running = true;
 | 
					 | 
				
			||||||
        usb_uart->thread = osThreadNew(usb_uart_worker, NULL, &usb_uart->thread_attr);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void usb_uart_disable() {
 | 
					 | 
				
			||||||
    if(usb_uart->running == true) {
 | 
					 | 
				
			||||||
        osThreadFlagsSet(usb_uart->thread, WorkerCmdStop);
 | 
					 | 
				
			||||||
        osSemaphoreRelease(usb_uart->rx_done_sem);
 | 
					 | 
				
			||||||
        osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny, osWaitForever);
 | 
					 | 
				
			||||||
        usb_uart->running = false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) {
 | 
					bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    //GpioApp* app = context;
 | 
					    //GpioApp* app = context;
 | 
				
			||||||
    bool consumed = false;
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        if(event.event == UsbUartLineIndexEnable) {
 | 
					        if(event.event == GPIO_SCENE_USB_UART_CUSTOM_EVENT_ENABLE) {
 | 
				
			||||||
            usb_uart_enable();
 | 
					            usb_uart_enable(cfg_set);
 | 
				
			||||||
        } else if(event.event == UsbUartLineIndexDisable) {
 | 
					        } else if(event.event == GPIO_SCENE_USB_UART_CUSTOM_EVENT_DISABLE) {
 | 
				
			||||||
            usb_uart_disable();
 | 
					            usb_uart_disable();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        consumed = true;
 | 
					        consumed = true;
 | 
				
			||||||
@ -271,15 +42,13 @@ bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
    return consumed;
 | 
					    return consumed;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Scene callbacks */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void line_vcp_cb(VariableItem* item) {
 | 
					static void line_vcp_cb(VariableItem* item) {
 | 
				
			||||||
    //GpioApp* app = variable_item_get_context(item);
 | 
					    //GpioApp* app = variable_item_get_context(item);
 | 
				
			||||||
    uint8_t index = variable_item_get_current_value_index(item);
 | 
					    uint8_t index = variable_item_get_current_value_index(item);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    variable_item_set_current_value_text(item, vcp_ch[index]);
 | 
					    variable_item_set_current_value_text(item, vcp_ch[index]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    usb_uart->cfg_set.vcp_ch = index;
 | 
					    cfg_set->vcp_ch = index;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void line_port_cb(VariableItem* item) {
 | 
					static void line_port_cb(VariableItem* item) {
 | 
				
			||||||
@ -288,34 +57,44 @@ static void line_port_cb(VariableItem* item) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    variable_item_set_current_value_text(item, uart_ch[index]);
 | 
					    variable_item_set_current_value_text(item, uart_ch[index]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    usb_uart->cfg_set.uart_ch = index;
 | 
					    if(index == 0)
 | 
				
			||||||
 | 
					        cfg_set->uart_ch = FuriHalUartIdUSART1;
 | 
				
			||||||
 | 
					    else if(index == 1)
 | 
				
			||||||
 | 
					        cfg_set->uart_ch = FuriHalUartIdLPUART1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void line_baudrate_cb(VariableItem* item) {
 | 
					static void line_baudrate_cb(VariableItem* item) {
 | 
				
			||||||
    //GpioApp* app = variable_item_get_context(item);
 | 
					    //GpioApp* app = variable_item_get_context(item);
 | 
				
			||||||
    uint8_t index = variable_item_get_current_value_index(item);
 | 
					    uint8_t index = variable_item_get_current_value_index(item);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    char br_text[8];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(index > 0) {
 | 
					    if(index > 0) {
 | 
				
			||||||
        snprintf(usb_uart->br_text, 7, "%lu", baudrate_list[index - 1]);
 | 
					        snprintf(br_text, 7, "%lu", baudrate_list[index - 1]);
 | 
				
			||||||
        variable_item_set_current_value_text(item, usb_uart->br_text);
 | 
					        variable_item_set_current_value_text(item, br_text);
 | 
				
			||||||
        usb_uart->cfg_set.baudrate = baudrate_list[index - 1];
 | 
					        cfg_set->baudrate = baudrate_list[index - 1];
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        variable_item_set_current_value_text(item, baudrate_mode[index]);
 | 
					        variable_item_set_current_value_text(item, baudrate_mode[index]);
 | 
				
			||||||
        usb_uart->cfg_set.baudrate = 0;
 | 
					        cfg_set->baudrate = 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void gpio_scene_usb_uart_enter_callback(void* context, uint32_t index) {
 | 
					static void gpio_scene_usb_uart_enter_callback(void* context, uint32_t index) {
 | 
				
			||||||
    furi_assert(context);
 | 
					    furi_assert(context);
 | 
				
			||||||
    GpioApp* app = context;
 | 
					    GpioApp* app = context;
 | 
				
			||||||
    view_dispatcher_send_custom_event(app->view_dispatcher, index);
 | 
					    if(index == UsbUartLineIndexEnable)
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(
 | 
				
			||||||
 | 
					            app->view_dispatcher, GPIO_SCENE_USB_UART_CUSTOM_EVENT_ENABLE);
 | 
				
			||||||
 | 
					    else if(index == UsbUartLineIndexDisable)
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(
 | 
				
			||||||
 | 
					            app->view_dispatcher, GPIO_SCENE_USB_UART_CUSTOM_EVENT_DISABLE);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void gpio_scene_usb_uart_on_enter(void* context) {
 | 
					void gpio_scene_usb_uart_on_enter(void* context) {
 | 
				
			||||||
    GpioApp* app = context;
 | 
					    GpioApp* app = context;
 | 
				
			||||||
    VariableItemList* var_item_list = app->var_item_list;
 | 
					    VariableItemList* var_item_list = app->var_item_list;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    usb_uart = furi_alloc(sizeof(UsbUartParams));
 | 
					    cfg_set = furi_alloc(sizeof(UsbUartConfig));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    VariableItem* item;
 | 
					    VariableItem* item;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -348,5 +127,5 @@ void gpio_scene_usb_uart_on_exit(void* context) {
 | 
				
			|||||||
    GpioApp* app = context;
 | 
					    GpioApp* app = context;
 | 
				
			||||||
    usb_uart_disable();
 | 
					    usb_uart_disable();
 | 
				
			||||||
    variable_item_list_clean(app->var_item_list);
 | 
					    variable_item_list_clean(app->var_item_list);
 | 
				
			||||||
    free(usb_uart);
 | 
					    free(cfg_set);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										246
									
								
								applications/gpio/usb_uart_bridge.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								applications/gpio/usb_uart_bridge.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,246 @@
 | 
				
			|||||||
 | 
					#include "usb_uart_bridge.h"
 | 
				
			||||||
 | 
					#include "furi-hal.h"
 | 
				
			||||||
 | 
					#include <stream_buffer.h>
 | 
				
			||||||
 | 
					#include <furi-hal-usb-cdc_i.h>
 | 
				
			||||||
 | 
					#include "usb_cdc.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define USB_PKT_LEN CDC_DATA_SZ
 | 
				
			||||||
 | 
					#define USB_UART_RX_BUF_SIZE (USB_PKT_LEN * 3)
 | 
				
			||||||
 | 
					#define USB_UART_TX_BUF_SIZE (USB_PKT_LEN * 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    WorkerEvtStop = (1 << 0),
 | 
				
			||||||
 | 
					    WorkerEvtRxReady = (1 << 1),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    WorkerEvtTxStop = (1 << 2),
 | 
				
			||||||
 | 
					    WorkerEvtTxReady = (1 << 3),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    WorkerEvtSof = (1 << 4),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} WorkerEvtFlags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxReady)
 | 
				
			||||||
 | 
					#define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtTxReady)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    UsbUartConfig cfg;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriThread* thread;
 | 
				
			||||||
 | 
					    FuriThread* tx_thread;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    osEventFlagsId_t events;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    StreamBufferHandle_t rx_stream;
 | 
				
			||||||
 | 
					    StreamBufferHandle_t tx_stream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t rx_buf[USB_PKT_LEN];
 | 
				
			||||||
 | 
					    uint8_t tx_buf[USB_PKT_LEN];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool buf_full;
 | 
				
			||||||
 | 
					} UsbUartParams;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static UsbUartParams* usb_uart;
 | 
				
			||||||
 | 
					static bool running = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void vcp_on_cdc_tx_complete();
 | 
				
			||||||
 | 
					static void vcp_on_cdc_rx();
 | 
				
			||||||
 | 
					static void vcp_state_callback(uint8_t state);
 | 
				
			||||||
 | 
					static void vcp_on_cdc_control_line(uint8_t state);
 | 
				
			||||||
 | 
					static void vcp_on_line_config(struct usb_cdc_line_coding* config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static CdcCallbacks cdc_cb = {
 | 
				
			||||||
 | 
					    vcp_on_cdc_tx_complete,
 | 
				
			||||||
 | 
					    vcp_on_cdc_rx,
 | 
				
			||||||
 | 
					    vcp_state_callback,
 | 
				
			||||||
 | 
					    vcp_on_cdc_control_line,
 | 
				
			||||||
 | 
					    vcp_on_line_config,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* USB UART worker */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int32_t usb_uart_tx_thread(void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data) {
 | 
				
			||||||
 | 
					    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(ev == UartIrqEventRXNE) {
 | 
				
			||||||
 | 
					        xStreamBufferSendFromISR(usb_uart->rx_stream, &data, 1, &xHigherPriorityTaskWoken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        size_t ret = xStreamBufferBytesAvailable(usb_uart->rx_stream);
 | 
				
			||||||
 | 
					        if(ret > USB_PKT_LEN) osEventFlagsSet(usb_uart->events, WorkerEvtRxReady);
 | 
				
			||||||
 | 
					    } else if(ev == UartIrqEventIDLE) {
 | 
				
			||||||
 | 
					        osEventFlagsSet(usb_uart->events, WorkerEvtRxReady);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int32_t usb_uart_worker(void* context) {
 | 
				
			||||||
 | 
					    memcpy(&usb_uart->cfg, context, sizeof(UsbUartConfig));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    usb_uart->rx_stream = xStreamBufferCreate(USB_UART_RX_BUF_SIZE, 1);
 | 
				
			||||||
 | 
					    usb_uart->tx_stream = xStreamBufferCreate(USB_UART_TX_BUF_SIZE, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    usb_uart->tx_thread = furi_thread_alloc();
 | 
				
			||||||
 | 
					    furi_thread_set_name(usb_uart->tx_thread, "usb_uart_tx");
 | 
				
			||||||
 | 
					    furi_thread_set_stack_size(usb_uart->tx_thread, 512);
 | 
				
			||||||
 | 
					    furi_thread_set_context(usb_uart->tx_thread, NULL);
 | 
				
			||||||
 | 
					    furi_thread_set_callback(usb_uart->tx_thread, usb_uart_tx_thread);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    UsbMode usb_mode_prev = furi_hal_usb_get_config();
 | 
				
			||||||
 | 
					    if(usb_uart->cfg.vcp_ch == 0) {
 | 
				
			||||||
 | 
					        furi_hal_usb_set_config(UsbModeVcpSingle);
 | 
				
			||||||
 | 
					        furi_hal_vcp_disable();
 | 
				
			||||||
 | 
					        osEventFlagsSet(usb_uart->events, WorkerEvtSof);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_hal_usb_set_config(UsbModeVcpDual);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(usb_uart->cfg.uart_ch == FuriHalUartIdUSART1) {
 | 
				
			||||||
 | 
					        furi_hal_console_disable();
 | 
				
			||||||
 | 
					    } else if(usb_uart->cfg.uart_ch == FuriHalUartIdLPUART1) {
 | 
				
			||||||
 | 
					        furi_hal_uart_init(usb_uart->cfg.uart_ch, 115200);
 | 
				
			||||||
 | 
					        furi_hal_uart_set_irq_cb(usb_uart->cfg.uart_ch, usb_uart_on_irq_cb);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_hal_uart_set_irq_cb(usb_uart->cfg.uart_ch, usb_uart_on_irq_cb);
 | 
				
			||||||
 | 
					    if(usb_uart->cfg.baudrate != 0)
 | 
				
			||||||
 | 
					        furi_hal_uart_set_br(usb_uart->cfg.uart_ch, usb_uart->cfg.baudrate);
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        vcp_on_line_config(furi_hal_cdc_get_port_settings(usb_uart->cfg.vcp_ch));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_hal_cdc_set_callbacks(usb_uart->cfg.vcp_ch, &cdc_cb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_thread_start(usb_uart->tx_thread);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while(1) {
 | 
				
			||||||
 | 
					        uint32_t events = osEventFlagsWait(
 | 
				
			||||||
 | 
					            usb_uart->events, WORKER_ALL_RX_EVENTS, osFlagsWaitAny, osWaitForever);
 | 
				
			||||||
 | 
					        furi_check((events & osFlagsError) == 0);
 | 
				
			||||||
 | 
					        if(events & WorkerEvtStop) break;
 | 
				
			||||||
 | 
					        if(events & WorkerEvtRxReady) {
 | 
				
			||||||
 | 
					            size_t len = 0;
 | 
				
			||||||
 | 
					            do {
 | 
				
			||||||
 | 
					                len = xStreamBufferReceive(usb_uart->rx_stream, usb_uart->rx_buf, USB_PKT_LEN, 0);
 | 
				
			||||||
 | 
					                if(len > 0) {
 | 
				
			||||||
 | 
					                    if((osEventFlagsWait(usb_uart->events, WorkerEvtSof, osFlagsWaitAny, 100) &
 | 
				
			||||||
 | 
					                        osFlagsError) == 0)
 | 
				
			||||||
 | 
					                        furi_hal_cdc_send(usb_uart->cfg.vcp_ch, usb_uart->rx_buf, len);
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                        xStreamBufferReset(usb_uart->rx_stream);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } while(len > 0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    osEventFlagsSet(usb_uart->events, WorkerEvtTxStop);
 | 
				
			||||||
 | 
					    furi_thread_join(usb_uart->tx_thread);
 | 
				
			||||||
 | 
					    furi_thread_free(usb_uart->tx_thread);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(usb_uart->cfg.uart_ch == FuriHalUartIdUSART1)
 | 
				
			||||||
 | 
					        furi_hal_console_enable();
 | 
				
			||||||
 | 
					    else if(usb_uart->cfg.uart_ch == FuriHalUartIdLPUART1)
 | 
				
			||||||
 | 
					        furi_hal_uart_deinit(usb_uart->cfg.uart_ch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_hal_cdc_set_callbacks(usb_uart->cfg.vcp_ch, NULL);
 | 
				
			||||||
 | 
					    furi_hal_usb_set_config(usb_mode_prev);
 | 
				
			||||||
 | 
					    if(usb_uart->cfg.vcp_ch == 0) furi_hal_vcp_enable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    vStreamBufferDelete(usb_uart->rx_stream);
 | 
				
			||||||
 | 
					    vStreamBufferDelete(usb_uart->tx_stream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int32_t usb_uart_tx_thread(void* context) {
 | 
				
			||||||
 | 
					    uint8_t data[USB_PKT_LEN];
 | 
				
			||||||
 | 
					    while(1) {
 | 
				
			||||||
 | 
					        uint32_t events = osEventFlagsWait(
 | 
				
			||||||
 | 
					            usb_uart->events, WORKER_ALL_TX_EVENTS, osFlagsWaitAny, osWaitForever);
 | 
				
			||||||
 | 
					        furi_check((events & osFlagsError) == 0);
 | 
				
			||||||
 | 
					        if(events & WorkerEvtTxStop) break;
 | 
				
			||||||
 | 
					        if(events & WorkerEvtTxReady) {
 | 
				
			||||||
 | 
					            size_t len = 0;
 | 
				
			||||||
 | 
					            do {
 | 
				
			||||||
 | 
					                len = xStreamBufferReceive(usb_uart->tx_stream, &data, 1, 0);
 | 
				
			||||||
 | 
					                if(len > 0) {
 | 
				
			||||||
 | 
					                    furi_hal_uart_tx(usb_uart->cfg.uart_ch, data, len);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if((usb_uart->buf_full == true) &&
 | 
				
			||||||
 | 
					                   (xStreamBufferBytesAvailable(usb_uart->tx_stream) == 0)) {
 | 
				
			||||||
 | 
					                    // Stream buffer was overflown, but now is free. Reading USB buffer to resume USB transfers
 | 
				
			||||||
 | 
					                    usb_uart->buf_full = false;
 | 
				
			||||||
 | 
					                    int32_t size = furi_hal_cdc_receive(usb_uart->cfg.vcp_ch, data, USB_PKT_LEN);
 | 
				
			||||||
 | 
					                    if(size > 0) {
 | 
				
			||||||
 | 
					                        furi_hal_uart_tx(usb_uart->cfg.uart_ch, data, size);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } while(len > 0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* VCP callbacks */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void vcp_on_cdc_tx_complete() {
 | 
				
			||||||
 | 
					    osEventFlagsSet(usb_uart->events, WorkerEvtSof);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void vcp_on_cdc_rx() {
 | 
				
			||||||
 | 
					    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint16_t max_len = xStreamBufferSpacesAvailable(usb_uart->tx_stream);
 | 
				
			||||||
 | 
					    if(max_len >= USB_PKT_LEN) {
 | 
				
			||||||
 | 
					        //if(max_len > USB_PKT_LEN) max_len = USB_PKT_LEN;
 | 
				
			||||||
 | 
					        int32_t size = furi_hal_cdc_receive(usb_uart->cfg.vcp_ch, usb_uart->tx_buf, USB_PKT_LEN);
 | 
				
			||||||
 | 
					        if(size > 0) {
 | 
				
			||||||
 | 
					            size_t ret = xStreamBufferSendFromISR(
 | 
				
			||||||
 | 
					                usb_uart->tx_stream, usb_uart->tx_buf, size, &xHigherPriorityTaskWoken);
 | 
				
			||||||
 | 
					            furi_check(ret == size);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        usb_uart->buf_full = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    osEventFlagsSet(usb_uart->events, WorkerEvtTxReady);
 | 
				
			||||||
 | 
					    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void vcp_state_callback(uint8_t state) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void vcp_on_cdc_control_line(uint8_t state) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void vcp_on_line_config(struct usb_cdc_line_coding* config) {
 | 
				
			||||||
 | 
					    if((usb_uart->cfg.baudrate == 0) && (config->dwDTERate != 0))
 | 
				
			||||||
 | 
					        furi_hal_uart_set_br(usb_uart->cfg.uart_ch, config->dwDTERate);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void usb_uart_enable(UsbUartConfig* cfg) {
 | 
				
			||||||
 | 
					    if(running == false) {
 | 
				
			||||||
 | 
					        running = true;
 | 
				
			||||||
 | 
					        usb_uart = furi_alloc(sizeof(UsbUartParams));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        usb_uart->thread = furi_thread_alloc();
 | 
				
			||||||
 | 
					        furi_thread_set_name(usb_uart->thread, "usb_uart");
 | 
				
			||||||
 | 
					        furi_thread_set_stack_size(usb_uart->thread, 1024);
 | 
				
			||||||
 | 
					        furi_thread_set_context(usb_uart->thread, cfg);
 | 
				
			||||||
 | 
					        furi_thread_set_callback(usb_uart->thread, usb_uart_worker);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        usb_uart->events = osEventFlagsNew(NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_thread_start(usb_uart->thread);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void usb_uart_disable() {
 | 
				
			||||||
 | 
					    if(running == true) {
 | 
				
			||||||
 | 
					        osEventFlagsSet(usb_uart->events, WorkerEvtStop);
 | 
				
			||||||
 | 
					        furi_thread_join(usb_uart->thread);
 | 
				
			||||||
 | 
					        furi_thread_free(usb_uart->thread);
 | 
				
			||||||
 | 
					        osEventFlagsDelete(usb_uart->events);
 | 
				
			||||||
 | 
					        free(usb_uart);
 | 
				
			||||||
 | 
					        running = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								applications/gpio/usb_uart_bridge.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								applications/gpio/usb_uart_bridge.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdint.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    uint8_t vcp_ch;
 | 
				
			||||||
 | 
					    uint8_t uart_ch;
 | 
				
			||||||
 | 
					    uint32_t baudrate;
 | 
				
			||||||
 | 
					} UsbUartConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void usb_uart_enable(UsbUartConfig* cfg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void usb_uart_disable();
 | 
				
			||||||
							
								
								
									
										36
									
								
								applications/gui/canvas.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										36
									
								
								applications/gui/canvas.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@ -6,22 +6,32 @@
 | 
				
			|||||||
#include <furi-hal.h>
 | 
					#include <furi-hal.h>
 | 
				
			||||||
#include <u8g2_glue.h>
 | 
					#include <u8g2_glue.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CanvasFontParameters canvas_font_params[FontTotalNumber] = {
 | 
				
			||||||
 | 
					    [FontPrimary] = {.leading_default = 12, .leading_min = 11, .height = 8, .descender = 2},
 | 
				
			||||||
 | 
					    [FontSecondary] = {.leading_default = 11, .leading_min = 9, .height = 7, .descender = 2},
 | 
				
			||||||
 | 
					    [FontKeyboard] = {.leading_default = 11, .leading_min = 9, .height = 7, .descender = 2},
 | 
				
			||||||
 | 
					    [FontBigNumbers] = {.leading_default = 18, .leading_min = 16, .height = 15, .descender = 0},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Canvas* canvas_init() {
 | 
					Canvas* canvas_init() {
 | 
				
			||||||
    Canvas* canvas = furi_alloc(sizeof(Canvas));
 | 
					    Canvas* canvas = furi_alloc(sizeof(Canvas));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    furi_hal_power_insomnia_enter();
 | 
					    furi_hal_power_insomnia_enter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    canvas->orientation = CanvasOrientationHorizontal;
 | 
					    // Setup u8g2
 | 
				
			||||||
    u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32);
 | 
					    u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32);
 | 
				
			||||||
 | 
					    canvas->orientation = CanvasOrientationHorizontal;
 | 
				
			||||||
    // send init sequence to the display, display is in sleep mode after this
 | 
					    // Initialize display
 | 
				
			||||||
    u8g2_InitDisplay(&canvas->fb);
 | 
					    u8g2_InitDisplay(&canvas->fb);
 | 
				
			||||||
    // wake up display
 | 
					    // Wake up display
 | 
				
			||||||
    u8g2_ClearBuffer(&canvas->fb);
 | 
					 | 
				
			||||||
    u8g2_SetPowerSave(&canvas->fb, 0);
 | 
					    u8g2_SetPowerSave(&canvas->fb, 0);
 | 
				
			||||||
    u8g2_SendBuffer(&canvas->fb);
 | 
					
 | 
				
			||||||
 | 
					    // Clear buffer and send to device
 | 
				
			||||||
 | 
					    canvas_clear(canvas);
 | 
				
			||||||
 | 
					    canvas_commit(canvas);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    furi_hal_power_insomnia_exit();
 | 
					    furi_hal_power_insomnia_exit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return canvas;
 | 
					    return canvas;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -32,9 +42,12 @@ void canvas_free(Canvas* canvas) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void canvas_reset(Canvas* canvas) {
 | 
					void canvas_reset(Canvas* canvas) {
 | 
				
			||||||
    furi_assert(canvas);
 | 
					    furi_assert(canvas);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    canvas_clear(canvas);
 | 
					    canvas_clear(canvas);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    canvas_set_color(canvas, ColorBlack);
 | 
					    canvas_set_color(canvas, ColorBlack);
 | 
				
			||||||
    canvas_set_font(canvas, FontSecondary);
 | 
					    canvas_set_font(canvas, FontSecondary);
 | 
				
			||||||
 | 
					    canvas_set_font_direction(canvas, CanvasFontDirectionLeftToRight);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void canvas_commit(Canvas* canvas) {
 | 
					void canvas_commit(Canvas* canvas) {
 | 
				
			||||||
@ -86,6 +99,12 @@ uint8_t canvas_current_font_height(Canvas* canvas) {
 | 
				
			|||||||
    return font_height;
 | 
					    return font_height;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CanvasFontParameters* canvas_get_font_params(Canvas* canvas, Font font) {
 | 
				
			||||||
 | 
					    furi_assert(canvas);
 | 
				
			||||||
 | 
					    furi_assert(font < FontTotalNumber);
 | 
				
			||||||
 | 
					    return (CanvasFontParameters*)&canvas_font_params[font];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void canvas_clear(Canvas* canvas) {
 | 
					void canvas_clear(Canvas* canvas) {
 | 
				
			||||||
    furi_assert(canvas);
 | 
					    furi_assert(canvas);
 | 
				
			||||||
    u8g2_ClearBuffer(&canvas->fb);
 | 
					    u8g2_ClearBuffer(&canvas->fb);
 | 
				
			||||||
@ -96,6 +115,11 @@ void canvas_set_color(Canvas* canvas, Color color) {
 | 
				
			|||||||
    u8g2_SetDrawColor(&canvas->fb, color);
 | 
					    u8g2_SetDrawColor(&canvas->fb, color);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void canvas_set_font_direction(Canvas* canvas, CanvasFontDirection dir) {
 | 
				
			||||||
 | 
					    furi_assert(canvas);
 | 
				
			||||||
 | 
					    u8g2_SetFontDirection(&canvas->fb, dir);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void canvas_invert_color(Canvas* canvas) {
 | 
					void canvas_invert_color(Canvas* canvas) {
 | 
				
			||||||
    canvas->fb.draw_color = !canvas->fb.draw_color;
 | 
					    canvas->fb.draw_color = !canvas->fb.draw_color;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,15 @@ typedef enum {
 | 
				
			|||||||
} Color;
 | 
					} Color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Fonts enumeration */
 | 
					/** Fonts enumeration */
 | 
				
			||||||
typedef enum { FontPrimary, FontSecondary, FontKeyboard, FontBigNumbers } Font;
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    FontPrimary,
 | 
				
			||||||
 | 
					    FontSecondary,
 | 
				
			||||||
 | 
					    FontKeyboard,
 | 
				
			||||||
 | 
					    FontBigNumbers,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Keep last for fonts number calculation
 | 
				
			||||||
 | 
					    FontTotalNumber,
 | 
				
			||||||
 | 
					} Font;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Alignment enumeration */
 | 
					/** Alignment enumeration */
 | 
				
			||||||
typedef enum {
 | 
					typedef enum {
 | 
				
			||||||
@ -37,6 +45,22 @@ typedef enum {
 | 
				
			|||||||
    CanvasOrientationVertical,
 | 
					    CanvasOrientationVertical,
 | 
				
			||||||
} CanvasOrientation;
 | 
					} CanvasOrientation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Font Direction */
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    CanvasFontDirectionLeftToRight,
 | 
				
			||||||
 | 
					    CanvasFontDirectionTopToDown,
 | 
				
			||||||
 | 
					    CanvasFontDirectionRightToLeft,
 | 
				
			||||||
 | 
					    CanvasFontDirectionDownToTop,
 | 
				
			||||||
 | 
					} CanvasFontDirection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Font parameters */
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    uint8_t leading_default;
 | 
				
			||||||
 | 
					    uint8_t leading_min;
 | 
				
			||||||
 | 
					    uint8_t height;
 | 
				
			||||||
 | 
					    uint8_t descender;
 | 
				
			||||||
 | 
					} CanvasFontParameters;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Canvas anonymouse structure */
 | 
					/** Canvas anonymouse structure */
 | 
				
			||||||
typedef struct Canvas Canvas;
 | 
					typedef struct Canvas Canvas;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -64,6 +88,15 @@ uint8_t canvas_height(Canvas* canvas);
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
uint8_t canvas_current_font_height(Canvas* canvas);
 | 
					uint8_t canvas_current_font_height(Canvas* canvas);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Get font parameters
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param      canvas  Canvas instance
 | 
				
			||||||
 | 
					 * @param      font    Font
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @return     pointer to CanvasFontParameters structure
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					CanvasFontParameters* canvas_get_font_params(Canvas* canvas, Font font);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Clear canvas
 | 
					/** Clear canvas
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @param      canvas  Canvas instance
 | 
					 * @param      canvas  Canvas instance
 | 
				
			||||||
@ -77,6 +110,14 @@ void canvas_clear(Canvas* canvas);
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
void canvas_set_color(Canvas* canvas, Color color);
 | 
					void canvas_set_color(Canvas* canvas, Color color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Set font swap
 | 
				
			||||||
 | 
					 * Argument String Rotation Description
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param      canvas  Canvas instance
 | 
				
			||||||
 | 
					 * @param      dir     Direction font
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void canvas_set_font_direction(Canvas* canvas, CanvasFontDirection dir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Invert drawing color
 | 
					/** Invert drawing color
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @param      canvas  Canvas instance
 | 
					 * @param      canvas  Canvas instance
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										215
									
								
								applications/gui/elements.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										215
									
								
								applications/gui/elements.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@ -10,6 +10,18 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <string.h>
 | 
					#include <string.h>
 | 
				
			||||||
#include <stdint.h>
 | 
					#include <stdint.h>
 | 
				
			||||||
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    uint8_t x;
 | 
				
			||||||
 | 
					    uint8_t y;
 | 
				
			||||||
 | 
					    uint8_t leading_min;
 | 
				
			||||||
 | 
					    uint8_t leading_default;
 | 
				
			||||||
 | 
					    uint8_t height;
 | 
				
			||||||
 | 
					    uint8_t descender;
 | 
				
			||||||
 | 
					    uint8_t len;
 | 
				
			||||||
 | 
					    const char* text;
 | 
				
			||||||
 | 
					} ElementTextBoxLine;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void elements_progress_bar(
 | 
					void elements_progress_bar(
 | 
				
			||||||
    Canvas* canvas,
 | 
					    Canvas* canvas,
 | 
				
			||||||
@ -352,3 +364,206 @@ void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) {
 | 
				
			|||||||
        string_cat(string, "...");
 | 
					        string_cat(string, "...");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void elements_text_box(
 | 
				
			||||||
 | 
					    Canvas* canvas,
 | 
				
			||||||
 | 
					    uint8_t x,
 | 
				
			||||||
 | 
					    uint8_t y,
 | 
				
			||||||
 | 
					    uint8_t width,
 | 
				
			||||||
 | 
					    uint8_t height,
 | 
				
			||||||
 | 
					    Align horizontal,
 | 
				
			||||||
 | 
					    Align vertical,
 | 
				
			||||||
 | 
					    const char* text) {
 | 
				
			||||||
 | 
					    furi_assert(canvas);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ElementTextBoxLine line[ELEMENTS_MAX_LINES_NUM];
 | 
				
			||||||
 | 
					    bool bold = false;
 | 
				
			||||||
 | 
					    bool mono = false;
 | 
				
			||||||
 | 
					    bool inversed = false;
 | 
				
			||||||
 | 
					    bool inversed_present = false;
 | 
				
			||||||
 | 
					    Font current_font = FontSecondary;
 | 
				
			||||||
 | 
					    Font prev_font = FontSecondary;
 | 
				
			||||||
 | 
					    CanvasFontParameters* font_params = canvas_get_font_params(canvas, current_font);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Fill line parameters
 | 
				
			||||||
 | 
					    uint8_t line_leading_min = font_params->leading_min;
 | 
				
			||||||
 | 
					    uint8_t line_leading_default = font_params->leading_default;
 | 
				
			||||||
 | 
					    uint8_t line_height = font_params->height;
 | 
				
			||||||
 | 
					    uint8_t line_descender = font_params->descender;
 | 
				
			||||||
 | 
					    uint8_t line_num = 0;
 | 
				
			||||||
 | 
					    uint8_t line_width = 0;
 | 
				
			||||||
 | 
					    uint8_t line_len = 0;
 | 
				
			||||||
 | 
					    uint8_t total_height_min = 0;
 | 
				
			||||||
 | 
					    uint8_t total_height_default = 0;
 | 
				
			||||||
 | 
					    uint16_t i = 0;
 | 
				
			||||||
 | 
					    bool full_text_processed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canvas_set_font(canvas, FontSecondary);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Fill all lines
 | 
				
			||||||
 | 
					    line[0].text = text;
 | 
				
			||||||
 | 
					    for(i = 0; !full_text_processed; i++) {
 | 
				
			||||||
 | 
					        line_len++;
 | 
				
			||||||
 | 
					        // Identify line height
 | 
				
			||||||
 | 
					        if(prev_font != current_font) {
 | 
				
			||||||
 | 
					            font_params = canvas_get_font_params(canvas, current_font);
 | 
				
			||||||
 | 
					            line_leading_min = MAX(line_leading_min, font_params->leading_min);
 | 
				
			||||||
 | 
					            line_leading_default = MAX(line_leading_default, font_params->leading_default);
 | 
				
			||||||
 | 
					            line_height = MAX(line_height, font_params->height);
 | 
				
			||||||
 | 
					            line_descender = MAX(line_descender, font_params->descender);
 | 
				
			||||||
 | 
					            prev_font = current_font;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // Set the font
 | 
				
			||||||
 | 
					        if(text[i] == '\e' && text[i + 1]) {
 | 
				
			||||||
 | 
					            i++;
 | 
				
			||||||
 | 
					            line_len++;
 | 
				
			||||||
 | 
					            if(text[i] == ELEMENTS_BOLD_MARKER) {
 | 
				
			||||||
 | 
					                if(bold) {
 | 
				
			||||||
 | 
					                    current_font = FontSecondary;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    current_font = FontPrimary;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                canvas_set_font(canvas, current_font);
 | 
				
			||||||
 | 
					                bold = !bold;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if(text[i] == ELEMENTS_MONO_MARKER) {
 | 
				
			||||||
 | 
					                if(mono) {
 | 
				
			||||||
 | 
					                    current_font = FontSecondary;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    current_font = FontKeyboard;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                canvas_set_font(canvas, FontKeyboard);
 | 
				
			||||||
 | 
					                mono = !mono;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if(text[i] == ELEMENTS_INVERSED_MARKER) {
 | 
				
			||||||
 | 
					                inversed_present = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(text[i] != '\n') {
 | 
				
			||||||
 | 
					            line_width += canvas_glyph_width(canvas, text[i]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // Process new line
 | 
				
			||||||
 | 
					        if(text[i] == '\n' || text[i] == '\0' || line_width > width) {
 | 
				
			||||||
 | 
					            if(line_width > width) {
 | 
				
			||||||
 | 
					                line_width -= canvas_glyph_width(canvas, text[i--]);
 | 
				
			||||||
 | 
					                line_len--;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if(text[i] == '\0') {
 | 
				
			||||||
 | 
					                full_text_processed = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if(inversed_present) {
 | 
				
			||||||
 | 
					                line_leading_min += 1;
 | 
				
			||||||
 | 
					                line_leading_default += 1;
 | 
				
			||||||
 | 
					                inversed_present = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            line[line_num].leading_min = line_leading_min;
 | 
				
			||||||
 | 
					            line[line_num].leading_default = line_leading_default;
 | 
				
			||||||
 | 
					            line[line_num].height = line_height;
 | 
				
			||||||
 | 
					            line[line_num].descender = line_descender;
 | 
				
			||||||
 | 
					            if(total_height_min + line_leading_min > height) {
 | 
				
			||||||
 | 
					                line_num--;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            total_height_min += line_leading_min;
 | 
				
			||||||
 | 
					            total_height_default += line_leading_default;
 | 
				
			||||||
 | 
					            line[line_num].len = line_len;
 | 
				
			||||||
 | 
					            if(horizontal == AlignCenter) {
 | 
				
			||||||
 | 
					                line[line_num].x = x + (width - line_width) / 2;
 | 
				
			||||||
 | 
					            } else if(horizontal == AlignRight) {
 | 
				
			||||||
 | 
					                line[line_num].x = x + (width - line_width);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                line[line_num].x = x;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            line[line_num].y = total_height_min;
 | 
				
			||||||
 | 
					            line_num++;
 | 
				
			||||||
 | 
					            if(text[i + 1]) {
 | 
				
			||||||
 | 
					                line[line_num].text = &text[i + 1];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            line_leading_min = font_params->leading_min;
 | 
				
			||||||
 | 
					            line_height = font_params->height;
 | 
				
			||||||
 | 
					            line_descender = font_params->descender;
 | 
				
			||||||
 | 
					            line_width = 0;
 | 
				
			||||||
 | 
					            line_len = 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set vertical alignment for all lines
 | 
				
			||||||
 | 
					    if(full_text_processed) {
 | 
				
			||||||
 | 
					        if(total_height_default < height) {
 | 
				
			||||||
 | 
					            if(vertical == AlignTop) {
 | 
				
			||||||
 | 
					                line[0].y = y + line[0].height;
 | 
				
			||||||
 | 
					            } else if(vertical == AlignCenter) {
 | 
				
			||||||
 | 
					                line[0].y = y + line[0].height + (height - total_height_default) / 2;
 | 
				
			||||||
 | 
					            } else if(vertical == AlignBottom) {
 | 
				
			||||||
 | 
					                line[0].y = y + line[0].height + (height - total_height_default);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if(line_num > 1) {
 | 
				
			||||||
 | 
					                for(uint8_t i = 1; i < line_num; i++) {
 | 
				
			||||||
 | 
					                    line[i].y = line[i - 1].y + line[i - 1].leading_default;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if(line_num > 1) {
 | 
				
			||||||
 | 
					            uint8_t free_pixel_num = height - total_height_min;
 | 
				
			||||||
 | 
					            uint8_t fill_pixel = 0;
 | 
				
			||||||
 | 
					            uint8_t j = 1;
 | 
				
			||||||
 | 
					            line[0].y = line[0].height;
 | 
				
			||||||
 | 
					            while(fill_pixel < free_pixel_num) {
 | 
				
			||||||
 | 
					                line[j].y = line[j - 1].y + line[j - 1].leading_min + 1;
 | 
				
			||||||
 | 
					                fill_pixel++;
 | 
				
			||||||
 | 
					                j = j % (line_num - 1) + 1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Draw line by line
 | 
				
			||||||
 | 
					    canvas_set_font(canvas, FontSecondary);
 | 
				
			||||||
 | 
					    bold = false;
 | 
				
			||||||
 | 
					    mono = false;
 | 
				
			||||||
 | 
					    inversed = false;
 | 
				
			||||||
 | 
					    for(uint8_t i = 0; i < line_num; i++) {
 | 
				
			||||||
 | 
					        for(uint8_t j = 0; j < line[i].len; j++) {
 | 
				
			||||||
 | 
					            // Process format symbols
 | 
				
			||||||
 | 
					            if(line[i].text[j] == ELEMENTS_BOLD_MARKER) {
 | 
				
			||||||
 | 
					                if(bold) {
 | 
				
			||||||
 | 
					                    current_font = FontSecondary;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    current_font = FontPrimary;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                canvas_set_font(canvas, current_font);
 | 
				
			||||||
 | 
					                bold = !bold;
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if(line[i].text[j] == ELEMENTS_MONO_MARKER) {
 | 
				
			||||||
 | 
					                if(mono) {
 | 
				
			||||||
 | 
					                    current_font = FontSecondary;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    current_font = FontKeyboard;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                canvas_set_font(canvas, current_font);
 | 
				
			||||||
 | 
					                mono = !mono;
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if(line[i].text[j] == ELEMENTS_INVERSED_MARKER) {
 | 
				
			||||||
 | 
					                inversed = !inversed;
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if(inversed) {
 | 
				
			||||||
 | 
					                canvas_draw_box(
 | 
				
			||||||
 | 
					                    canvas,
 | 
				
			||||||
 | 
					                    line[i].x - 1,
 | 
				
			||||||
 | 
					                    line[i].y - line[i].height - 1,
 | 
				
			||||||
 | 
					                    canvas_glyph_width(canvas, line[i].text[j]) + 1,
 | 
				
			||||||
 | 
					                    line[i].height + line[i].descender + 2);
 | 
				
			||||||
 | 
					                canvas_invert_color(canvas);
 | 
				
			||||||
 | 
					                canvas_draw_glyph(canvas, line[i].x, line[i].y, line[i].text[j]);
 | 
				
			||||||
 | 
					                canvas_invert_color(canvas);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                canvas_draw_glyph(canvas, line[i].x, line[i].y, line[i].text[j]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            line[i].x += canvas_glyph_width(canvas, line[i].text[j]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    canvas_set_font(canvas, FontSecondary);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										127
									
								
								applications/gui/elements.h
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										127
									
								
								applications/gui/elements.h
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@ -16,12 +16,19 @@
 | 
				
			|||||||
extern "C" {
 | 
					extern "C" {
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ELEMENTS_MAX_LINES_NUM (7)
 | 
				
			||||||
 | 
					#define ELEMENTS_BOLD_MARKER '#'
 | 
				
			||||||
 | 
					#define ELEMENTS_MONO_MARKER '*'
 | 
				
			||||||
 | 
					#define ELEMENTS_INVERSED_MARKER '!'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw progress bar.
 | 
					/** Draw progress bar.
 | 
				
			||||||
 * @param x - progress bar position on X axis
 | 
					 *
 | 
				
			||||||
 * @param y - progress bar position on Y axis
 | 
					 * @param   canvas      Canvas instance
 | 
				
			||||||
 * @param width - progress bar width
 | 
					 * @param   x           progress bar position on X axis
 | 
				
			||||||
 * @param progress - progress in unnamed metric
 | 
					 * @param   y           progress bar position on Y axis
 | 
				
			||||||
 * @param total - total amount in unnamed metric
 | 
					 * @param   width       progress bar width
 | 
				
			||||||
 | 
					 * @param   progress    progress in unnamed metric
 | 
				
			||||||
 | 
					 * @param   total       total amount in unnamed metric
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_progress_bar(
 | 
					void elements_progress_bar(
 | 
				
			||||||
    Canvas* canvas,
 | 
					    Canvas* canvas,
 | 
				
			||||||
@ -32,11 +39,13 @@ void elements_progress_bar(
 | 
				
			|||||||
    uint8_t total);
 | 
					    uint8_t total);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw scrollbar on canvas at specific position.
 | 
					/** Draw scrollbar on canvas at specific position.
 | 
				
			||||||
 * @param x - scrollbar position on X axis
 | 
					 *
 | 
				
			||||||
 * @param y - scrollbar position on Y axis
 | 
					 * @param   canvas  Canvas instance
 | 
				
			||||||
 * @param height - scrollbar height
 | 
					 * @param   x       scrollbar position on X axis
 | 
				
			||||||
 * @param pos - current element 
 | 
					 * @param   y       scrollbar position on Y axis
 | 
				
			||||||
 * @param total - total elements
 | 
					 * @param   height  scrollbar height
 | 
				
			||||||
 | 
					 * @param   pos     current element
 | 
				
			||||||
 | 
					 * @param   total   total elements
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_scrollbar_pos(
 | 
					void elements_scrollbar_pos(
 | 
				
			||||||
    Canvas* canvas,
 | 
					    Canvas* canvas,
 | 
				
			||||||
@ -47,37 +56,49 @@ void elements_scrollbar_pos(
 | 
				
			|||||||
    uint16_t total);
 | 
					    uint16_t total);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw scrollbar on canvas.
 | 
					/** Draw scrollbar on canvas.
 | 
				
			||||||
 * width 3px, height equal to canvas height
 | 
					 * @note    width 3px, height equal to canvas height
 | 
				
			||||||
 * @param pos - current element of total elements
 | 
					 *
 | 
				
			||||||
 * @param total - total elements
 | 
					 * @param   canvas  Canvas instance
 | 
				
			||||||
 | 
					 * @param   pos     current element of total elements
 | 
				
			||||||
 | 
					 * @param   total   total elements
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_scrollbar(Canvas* canvas, uint16_t pos, uint16_t total);
 | 
					void elements_scrollbar(Canvas* canvas, uint16_t pos, uint16_t total);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw rounded frame
 | 
					/** Draw rounded frame
 | 
				
			||||||
 * @param x, y - top left corner coordinates
 | 
					 *
 | 
				
			||||||
 * @param width, height - frame width and height
 | 
					 * @param   canvas          Canvas instance
 | 
				
			||||||
 | 
					 * @param   x, y            top left corner coordinates
 | 
				
			||||||
 | 
					 * @param   width, height   frame width and height
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height);
 | 
					void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw button in left corner
 | 
					/** Draw button in left corner
 | 
				
			||||||
 * @param str - button text
 | 
					 *
 | 
				
			||||||
 | 
					 * @param   canvas  Canvas instance
 | 
				
			||||||
 | 
					 * @param   str     button text
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_button_left(Canvas* canvas, const char* str);
 | 
					void elements_button_left(Canvas* canvas, const char* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw button in right corner
 | 
					/** Draw button in right corner
 | 
				
			||||||
 * @param str - button text
 | 
					 *
 | 
				
			||||||
 | 
					 * @param   canvas  Canvas instance
 | 
				
			||||||
 | 
					 * @param   str     button text
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_button_right(Canvas* canvas, const char* str);
 | 
					void elements_button_right(Canvas* canvas, const char* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw button in center
 | 
					/** Draw button in center
 | 
				
			||||||
 * @param str - button text
 | 
					 *
 | 
				
			||||||
 | 
					 * @param   canvas  Canvas instance
 | 
				
			||||||
 | 
					 * @param   str     button text
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_button_center(Canvas* canvas, const char* str);
 | 
					void elements_button_center(Canvas* canvas, const char* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw aligned multiline text
 | 
					/** Draw aligned multiline text
 | 
				
			||||||
 * @param x, y - coordinates based on align param
 | 
					 *
 | 
				
			||||||
 * @param horizontal, vertical - aligment of multiline text
 | 
					 * @param   canvas                  Canvas instance
 | 
				
			||||||
 * @param text - string (possible multiline)
 | 
					 * @param   x, y                    coordinates based on align param
 | 
				
			||||||
 | 
					 * @param   horizontal, vertical    aligment of multiline text
 | 
				
			||||||
 | 
					 * @param   text                    string (possible multiline)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_multiline_text_aligned(
 | 
					void elements_multiline_text_aligned(
 | 
				
			||||||
    Canvas* canvas,
 | 
					    Canvas* canvas,
 | 
				
			||||||
@ -88,20 +109,26 @@ void elements_multiline_text_aligned(
 | 
				
			|||||||
    const char* text);
 | 
					    const char* text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw multiline text
 | 
					/** Draw multiline text
 | 
				
			||||||
 * @param x, y - top left corner coordinates
 | 
					 *
 | 
				
			||||||
 * @param text - string (possible multiline)
 | 
					 * @param   canvas  Canvas instance
 | 
				
			||||||
 | 
					 * @param   x, y    top left corner coordinates
 | 
				
			||||||
 | 
					 * @param   text    string (possible multiline)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, const char* text);
 | 
					void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, const char* text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw framed multiline text
 | 
					/** Draw framed multiline text
 | 
				
			||||||
 * @param x, y - top left corner coordinates
 | 
					 *
 | 
				
			||||||
 * @param text - string (possible multiline)
 | 
					 * @param   canvas  Canvas instance
 | 
				
			||||||
 | 
					 * @param   x, y    top left corner coordinates
 | 
				
			||||||
 | 
					 * @param   text    string (possible multiline)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_multiline_text_framed(Canvas* canvas, uint8_t x, uint8_t y, const char* text);
 | 
					void elements_multiline_text_framed(Canvas* canvas, uint8_t x, uint8_t y, const char* text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw slightly rounded frame
 | 
					/** Draw slightly rounded frame
 | 
				
			||||||
 * @param x, y - top left corner coordinates
 | 
					 *
 | 
				
			||||||
 * @param width, height - size of frame
 | 
					 * @param   canvas          Canvas instance
 | 
				
			||||||
 | 
					 * @param   x, y            top left corner coordinates
 | 
				
			||||||
 | 
					 * @param   width, height   size of frame
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_slightly_rounded_frame(
 | 
					void elements_slightly_rounded_frame(
 | 
				
			||||||
    Canvas* canvas,
 | 
					    Canvas* canvas,
 | 
				
			||||||
@ -111,8 +138,10 @@ void elements_slightly_rounded_frame(
 | 
				
			|||||||
    uint8_t height);
 | 
					    uint8_t height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw slightly rounded box
 | 
					/** Draw slightly rounded box
 | 
				
			||||||
 * @param x, y - top left corner coordinates
 | 
					 *
 | 
				
			||||||
 * @param width, height - size of box
 | 
					 * @param   canvas          Canvas instance
 | 
				
			||||||
 | 
					 * @param   x, y            top left corner coordinates
 | 
				
			||||||
 | 
					 * @param   width, height   size of box
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_slightly_rounded_box(
 | 
					void elements_slightly_rounded_box(
 | 
				
			||||||
    Canvas* canvas,
 | 
					    Canvas* canvas,
 | 
				
			||||||
@ -122,19 +151,47 @@ void elements_slightly_rounded_box(
 | 
				
			|||||||
    uint8_t height);
 | 
					    uint8_t height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Draw bubble frame for text
 | 
					/** Draw bubble frame for text
 | 
				
			||||||
 * @param x - left x coordinates
 | 
					 *
 | 
				
			||||||
 * @param y - top y coordinate
 | 
					 * @param   canvas  Canvas instance
 | 
				
			||||||
 * @param width - bubble width
 | 
					 * @param   x       left x coordinates
 | 
				
			||||||
 * @param height - bubble height
 | 
					 * @param   y       top y coordinate
 | 
				
			||||||
 | 
					 * @param   width   bubble width
 | 
				
			||||||
 | 
					 * @param   height  bubble height
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height);
 | 
					void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Trim string buffer to fit width in pixels
 | 
					/** Trim string buffer to fit width in pixels
 | 
				
			||||||
 * @param string - string to trim
 | 
					 *
 | 
				
			||||||
 * @param width - max width
 | 
					 * @param   canvas  Canvas instance
 | 
				
			||||||
 | 
					 * @param   string  string to trim
 | 
				
			||||||
 | 
					 * @param   width   max width
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width);
 | 
					void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Draw text box element
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param       canvas      Canvas instance
 | 
				
			||||||
 | 
					 * @param       x           x coordinate
 | 
				
			||||||
 | 
					 * @param       y           y coordinate
 | 
				
			||||||
 | 
					 * @param       width       width to fit text
 | 
				
			||||||
 | 
					 * @param       height      height to fit text
 | 
				
			||||||
 | 
					 * @param       horizontal  Align instance
 | 
				
			||||||
 | 
					 * @param       vertical    Align instance
 | 
				
			||||||
 | 
					 * @param[in]   text        Formatted text. The following formats are available:
 | 
				
			||||||
 | 
					 *                          "\e#Bold text\e#" - bold font is used
 | 
				
			||||||
 | 
					 *                          "\e*Monospaced text\e*" - monospaced font is used
 | 
				
			||||||
 | 
					 *                          "\e#Inversed text\e#" - white text on black background
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void elements_text_box(
 | 
				
			||||||
 | 
					    Canvas* canvas,
 | 
				
			||||||
 | 
					    uint8_t x,
 | 
				
			||||||
 | 
					    uint8_t y,
 | 
				
			||||||
 | 
					    uint8_t width,
 | 
				
			||||||
 | 
					    uint8_t height,
 | 
				
			||||||
 | 
					    Align horizontal,
 | 
				
			||||||
 | 
					    Align vertical,
 | 
				
			||||||
 | 
					    const char* text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef __cplusplus
 | 
					#ifdef __cplusplus
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										475
									
								
								applications/gui/modules/code_input.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										475
									
								
								applications/gui/modules/code_input.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,475 @@
 | 
				
			|||||||
 | 
					#include "code_input.h"
 | 
				
			||||||
 | 
					#include <gui/elements.h>
 | 
				
			||||||
 | 
					#include <furi.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define MAX_CODE_LEN 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct CodeInput {
 | 
				
			||||||
 | 
					    View* view;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    CodeInputStateVerify,
 | 
				
			||||||
 | 
					    CodeInputStateUpdate,
 | 
				
			||||||
 | 
					    CodeInputStateTotal,
 | 
				
			||||||
 | 
					} CodeInputStateEnum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    CodeInputFirst,
 | 
				
			||||||
 | 
					    CodeInputSecond,
 | 
				
			||||||
 | 
					    CodeInputTotal,
 | 
				
			||||||
 | 
					} CodeInputsEnum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    uint8_t state;
 | 
				
			||||||
 | 
					    uint8_t current;
 | 
				
			||||||
 | 
					    bool ext_update;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t input_length[CodeInputTotal];
 | 
				
			||||||
 | 
					    uint8_t local_buffer[CodeInputTotal][MAX_CODE_LEN];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    CodeInputOkCallback ok_callback;
 | 
				
			||||||
 | 
					    CodeInputFailCallback fail_callback;
 | 
				
			||||||
 | 
					    void* callback_context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const char* header;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t* ext_buffer;
 | 
				
			||||||
 | 
					    uint8_t* ext_buffer_length;
 | 
				
			||||||
 | 
					} CodeInputModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const Icon* keys_assets[] = {
 | 
				
			||||||
 | 
					    [InputKeyUp] = &I_ButtonUp_7x4,
 | 
				
			||||||
 | 
					    [InputKeyDown] = &I_ButtonDown_7x4,
 | 
				
			||||||
 | 
					    [InputKeyRight] = &I_ButtonRight_4x7,
 | 
				
			||||||
 | 
					    [InputKeyLeft] = &I_ButtonLeft_4x7,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Compare buffers
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param in Input buffer pointer
 | 
				
			||||||
 | 
					 * @param len_in Input array length
 | 
				
			||||||
 | 
					 * @param src Source buffer pointer
 | 
				
			||||||
 | 
					 * @param len_src Source array length
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool code_input_compare(uint8_t* in, size_t len_in, uint8_t* src, size_t len_src) {
 | 
				
			||||||
 | 
					    bool result = false;
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        result = (len_in && len_src);
 | 
				
			||||||
 | 
					        if(!result) {
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        result = (len_in == len_src);
 | 
				
			||||||
 | 
					        if(!result) {
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < len_in; i++) {
 | 
				
			||||||
 | 
					            result = (in[i] == src[i]);
 | 
				
			||||||
 | 
					            if(!result) {
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Compare local buffers
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param model 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static bool code_input_compare_local(CodeInputModel* model) {
 | 
				
			||||||
 | 
					    uint8_t* source = model->local_buffer[CodeInputFirst];
 | 
				
			||||||
 | 
					    size_t source_length = model->input_length[CodeInputFirst];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t* input = model->local_buffer[CodeInputSecond];
 | 
				
			||||||
 | 
					    size_t input_length = model->input_length[CodeInputSecond];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return code_input_compare(input, input_length, source, source_length);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Compare ext with local
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param model 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static bool code_input_compare_ext(CodeInputModel* model) {
 | 
				
			||||||
 | 
					    uint8_t* input = model->local_buffer[CodeInputFirst];
 | 
				
			||||||
 | 
					    size_t input_length = model->input_length[CodeInputFirst];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t* source = model->ext_buffer;
 | 
				
			||||||
 | 
					    size_t source_length = *model->ext_buffer_length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return code_input_compare(input, input_length, source, source_length);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Set ext buffer
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param model 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static void code_input_set_ext(CodeInputModel* model) {
 | 
				
			||||||
 | 
					    *model->ext_buffer_length = model->input_length[CodeInputFirst];
 | 
				
			||||||
 | 
					    for(size_t i = 0; i <= model->input_length[CodeInputFirst]; i++) {
 | 
				
			||||||
 | 
					        model->ext_buffer[i] = model->local_buffer[CodeInputFirst][i];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Draw input sequence
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param canvas 
 | 
				
			||||||
 | 
					 * @param buffer 
 | 
				
			||||||
 | 
					 * @param length 
 | 
				
			||||||
 | 
					 * @param x 
 | 
				
			||||||
 | 
					 * @param y 
 | 
				
			||||||
 | 
					 * @param active
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static void code_input_draw_sequence(
 | 
				
			||||||
 | 
					    Canvas* canvas,
 | 
				
			||||||
 | 
					    uint8_t* buffer,
 | 
				
			||||||
 | 
					    uint8_t length,
 | 
				
			||||||
 | 
					    uint8_t x,
 | 
				
			||||||
 | 
					    uint8_t y,
 | 
				
			||||||
 | 
					    bool active) {
 | 
				
			||||||
 | 
					    uint8_t pos_x = x + 6;
 | 
				
			||||||
 | 
					    uint8_t pos_y = y + 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(active) canvas_draw_icon(canvas, x - 4, y + 5, &I_ButtonRightSmall_3x5);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elements_slightly_rounded_frame(canvas, x, y, 116, 15);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < length; i++) {
 | 
				
			||||||
 | 
					        // maybe symmetrical assets? :-/
 | 
				
			||||||
 | 
					        uint8_t offset_y = buffer[i] < 2 ? 2 + (buffer[i] * 2) : 1;
 | 
				
			||||||
 | 
					        canvas_draw_icon(canvas, pos_x, pos_y + offset_y, keys_assets[buffer[i]]);
 | 
				
			||||||
 | 
					        pos_x += buffer[i] > 1 ? 9 : 11;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Reset input count
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param model 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static void code_input_reset_count(CodeInputModel* model) {
 | 
				
			||||||
 | 
					    model->input_length[model->current] = 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Call input callback
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param model 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static void code_input_call_ok_callback(CodeInputModel* model) {
 | 
				
			||||||
 | 
					    if(model->ok_callback != NULL) {
 | 
				
			||||||
 | 
					        model->ok_callback(model->callback_context);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Call changed callback
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param model 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static void code_input_call_fail_callback(CodeInputModel* model) {
 | 
				
			||||||
 | 
					    if(model->fail_callback != NULL) {
 | 
				
			||||||
 | 
					        model->fail_callback(model->callback_context);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Handle Back button
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param model 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static bool code_input_handle_back(CodeInputModel* model) {
 | 
				
			||||||
 | 
					    if(model->current && !model->input_length[model->current]) {
 | 
				
			||||||
 | 
					        --model->current;
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(model->input_length[model->current]) {
 | 
				
			||||||
 | 
					        code_input_reset_count(model);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    code_input_call_fail_callback(model);
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Handle OK button
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param model 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static void code_input_handle_ok(CodeInputModel* model) {
 | 
				
			||||||
 | 
					    switch(model->state) {
 | 
				
			||||||
 | 
					    case CodeInputStateVerify:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(code_input_compare_ext(model)) {
 | 
				
			||||||
 | 
					            if(model->ext_update) {
 | 
				
			||||||
 | 
					                model->state = CodeInputStateUpdate;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                code_input_call_ok_callback(model);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        code_input_reset_count(model);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case CodeInputStateUpdate:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(!model->current && model->input_length[model->current]) {
 | 
				
			||||||
 | 
					            model->current++;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            if(code_input_compare_local(model)) {
 | 
				
			||||||
 | 
					                if(model->ext_update) {
 | 
				
			||||||
 | 
					                    code_input_set_ext(model);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                code_input_call_ok_callback(model);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                code_input_reset_count(model);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Handle input
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param model 
 | 
				
			||||||
 | 
					 * @param key 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					size_t code_input_push(uint8_t* buffer, size_t length, InputKey key) {
 | 
				
			||||||
 | 
					    buffer[length] = key;
 | 
				
			||||||
 | 
					    length = CLAMP(length + 1, MAX_CODE_LEN, 0);
 | 
				
			||||||
 | 
					    return length;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Handle D-pad keys
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param model 
 | 
				
			||||||
 | 
					 * @param key 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static void code_input_handle_dpad(CodeInputModel* model, InputKey key) {
 | 
				
			||||||
 | 
					    uint8_t at = model->current;
 | 
				
			||||||
 | 
					    size_t new_length = code_input_push(model->local_buffer[at], model->input_length[at], key);
 | 
				
			||||||
 | 
					    model->input_length[at] = new_length;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Draw callback
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param canvas 
 | 
				
			||||||
 | 
					 * @param _model 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static void code_input_view_draw_callback(Canvas* canvas, void* _model) {
 | 
				
			||||||
 | 
					    CodeInputModel* model = _model;
 | 
				
			||||||
 | 
					    uint8_t y_offset = 0;
 | 
				
			||||||
 | 
					    if(!strlen(model->header)) y_offset = 5;
 | 
				
			||||||
 | 
					    canvas_clear(canvas);
 | 
				
			||||||
 | 
					    canvas_set_color(canvas, ColorBlack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canvas_draw_str(canvas, 2, 9, model->header);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canvas_set_font(canvas, FontSecondary);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch(model->state) {
 | 
				
			||||||
 | 
					    case CodeInputStateVerify:
 | 
				
			||||||
 | 
					        code_input_draw_sequence(
 | 
				
			||||||
 | 
					            canvas,
 | 
				
			||||||
 | 
					            model->local_buffer[CodeInputFirst],
 | 
				
			||||||
 | 
					            model->input_length[CodeInputFirst],
 | 
				
			||||||
 | 
					            6,
 | 
				
			||||||
 | 
					            30 - y_offset,
 | 
				
			||||||
 | 
					            true);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    case CodeInputStateUpdate:
 | 
				
			||||||
 | 
					        code_input_draw_sequence(
 | 
				
			||||||
 | 
					            canvas,
 | 
				
			||||||
 | 
					            model->local_buffer[CodeInputFirst],
 | 
				
			||||||
 | 
					            model->input_length[CodeInputFirst],
 | 
				
			||||||
 | 
					            6,
 | 
				
			||||||
 | 
					            14 - y_offset,
 | 
				
			||||||
 | 
					            !model->current);
 | 
				
			||||||
 | 
					        code_input_draw_sequence(
 | 
				
			||||||
 | 
					            canvas,
 | 
				
			||||||
 | 
					            model->local_buffer[CodeInputSecond],
 | 
				
			||||||
 | 
					            model->input_length[CodeInputSecond],
 | 
				
			||||||
 | 
					            6,
 | 
				
			||||||
 | 
					            44 - y_offset,
 | 
				
			||||||
 | 
					            model->current);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(model->current) canvas_draw_str(canvas, 2, 39 - y_offset, "Repeat code");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Input callback
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param event 
 | 
				
			||||||
 | 
					 * @param context 
 | 
				
			||||||
 | 
					 * @return true 
 | 
				
			||||||
 | 
					 * @return false 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static bool code_input_view_input_callback(InputEvent* event, void* context) {
 | 
				
			||||||
 | 
					    CodeInput* code_input = context;
 | 
				
			||||||
 | 
					    furi_assert(code_input);
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
 | 
				
			||||||
 | 
					        switch(event->key) {
 | 
				
			||||||
 | 
					        case InputKeyBack:
 | 
				
			||||||
 | 
					            with_view_model(
 | 
				
			||||||
 | 
					                code_input->view, (CodeInputModel * model) {
 | 
				
			||||||
 | 
					                    consumed = code_input_handle_back(model);
 | 
				
			||||||
 | 
					                    return true;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case InputKeyOk:
 | 
				
			||||||
 | 
					            with_view_model(
 | 
				
			||||||
 | 
					                code_input->view, (CodeInputModel * model) {
 | 
				
			||||||
 | 
					                    code_input_handle_ok(model);
 | 
				
			||||||
 | 
					                    return true;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            with_view_model(
 | 
				
			||||||
 | 
					                code_input->view, (CodeInputModel * model) {
 | 
				
			||||||
 | 
					                    code_input_handle_dpad(model, event->key);
 | 
				
			||||||
 | 
					                    return true;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Reset all input-related data in model
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param model CodeInputModel
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static void code_input_reset_model_input_data(CodeInputModel* model) {
 | 
				
			||||||
 | 
					    model->current = 0;
 | 
				
			||||||
 | 
					    model->input_length[CodeInputFirst] = 0;
 | 
				
			||||||
 | 
					    model->input_length[CodeInputSecond] = 0;
 | 
				
			||||||
 | 
					    model->ext_buffer = NULL;
 | 
				
			||||||
 | 
					    model->ext_update = false;
 | 
				
			||||||
 | 
					    model->state = 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 
 | 
				
			||||||
 | 
					 * @brief Allocate and initialize code input. This code input is used to enter codes.
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @return CodeInput instance pointer
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					CodeInput* code_input_alloc() {
 | 
				
			||||||
 | 
					    CodeInput* code_input = furi_alloc(sizeof(CodeInput));
 | 
				
			||||||
 | 
					    code_input->view = view_alloc();
 | 
				
			||||||
 | 
					    view_set_context(code_input->view, code_input);
 | 
				
			||||||
 | 
					    view_allocate_model(code_input->view, ViewModelTypeLocking, sizeof(CodeInputModel));
 | 
				
			||||||
 | 
					    view_set_draw_callback(code_input->view, code_input_view_draw_callback);
 | 
				
			||||||
 | 
					    view_set_input_callback(code_input->view, code_input_view_input_callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        code_input->view, (CodeInputModel * model) {
 | 
				
			||||||
 | 
					            model->header = "";
 | 
				
			||||||
 | 
					            model->ok_callback = NULL;
 | 
				
			||||||
 | 
					            model->fail_callback = NULL;
 | 
				
			||||||
 | 
					            model->callback_context = NULL;
 | 
				
			||||||
 | 
					            code_input_reset_model_input_data(model);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return code_input;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 
 | 
				
			||||||
 | 
					 * @brief Deinitialize and free code input
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param code_input Code input instance
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void code_input_free(CodeInput* code_input) {
 | 
				
			||||||
 | 
					    furi_assert(code_input);
 | 
				
			||||||
 | 
					    view_free(code_input->view);
 | 
				
			||||||
 | 
					    free(code_input);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 
 | 
				
			||||||
 | 
					 * @brief Get code input view
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param code_input code input instance
 | 
				
			||||||
 | 
					 * @return View instance that can be used for embedding
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					View* code_input_get_view(CodeInput* code_input) {
 | 
				
			||||||
 | 
					    furi_assert(code_input);
 | 
				
			||||||
 | 
					    return code_input->view;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 
 | 
				
			||||||
 | 
					 * @brief Set code input callbacks
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param code_input code input instance
 | 
				
			||||||
 | 
					 * @param ok_callback input callback fn
 | 
				
			||||||
 | 
					 * @param fail_callback code match callback fn
 | 
				
			||||||
 | 
					 * @param callback_context callback context
 | 
				
			||||||
 | 
					 * @param buffer buffer 
 | 
				
			||||||
 | 
					 * @param buffer_length ptr to buffer length uint
 | 
				
			||||||
 | 
					 * @param ext_update  true to update buffer 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void code_input_set_result_callback(
 | 
				
			||||||
 | 
					    CodeInput* code_input,
 | 
				
			||||||
 | 
					    CodeInputOkCallback ok_callback,
 | 
				
			||||||
 | 
					    CodeInputFailCallback fail_callback,
 | 
				
			||||||
 | 
					    void* callback_context,
 | 
				
			||||||
 | 
					    uint8_t* buffer,
 | 
				
			||||||
 | 
					    uint8_t* buffer_length,
 | 
				
			||||||
 | 
					    bool ext_update) {
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        code_input->view, (CodeInputModel * model) {
 | 
				
			||||||
 | 
					            code_input_reset_model_input_data(model);
 | 
				
			||||||
 | 
					            model->ok_callback = ok_callback;
 | 
				
			||||||
 | 
					            model->fail_callback = fail_callback;
 | 
				
			||||||
 | 
					            model->callback_context = callback_context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            model->ext_buffer = buffer;
 | 
				
			||||||
 | 
					            model->ext_buffer_length = buffer_length;
 | 
				
			||||||
 | 
					            model->state = (*buffer_length == 0) ? 1 : 0;
 | 
				
			||||||
 | 
					            model->ext_update = ext_update;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Set code input header text
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param code_input code input instance
 | 
				
			||||||
 | 
					 * @param text text to be shown
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void code_input_set_header_text(CodeInput* code_input, const char* text) {
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        code_input->view, (CodeInputModel * model) {
 | 
				
			||||||
 | 
					            model->header = text;
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										91
									
								
								applications/gui/modules/code_input.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								applications/gui/modules/code_input.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @file code_input.h
 | 
				
			||||||
 | 
					 * GUI: CodeInput keyboard view module API
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <gui/view.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
 | 
					extern "C" {
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Code input anonymous structure  */
 | 
				
			||||||
 | 
					typedef struct CodeInput CodeInput;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** callback that is executed when entered code matches ext buffer */
 | 
				
			||||||
 | 
					typedef void (*CodeInputOkCallback)(void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** callback that is executed when entered code does not matches ext buffer */
 | 
				
			||||||
 | 
					typedef void (*CodeInputFailCallback)(void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Allocate and initialize code input. This code input is used to enter codes.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @return     CodeInput instance pointer
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					CodeInput* code_input_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Deinitialize and free code input
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param      code_input  Code input instance
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void code_input_free(CodeInput* code_input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Get code input view
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param      code_input  code input instance
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @return     View instance that can be used for embedding
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					View* code_input_get_view(CodeInput* code_input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Set code input result callback
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param      code_input        code input instance
 | 
				
			||||||
 | 
					 * @param      ok_callback    ok callback fn
 | 
				
			||||||
 | 
					 * @param      fail_callback  fail callback fn
 | 
				
			||||||
 | 
					 * @param      callback_context  callback context
 | 
				
			||||||
 | 
					 * @param      buffer       buffer to use
 | 
				
			||||||
 | 
					 * @param      buffer_length       buffer length
 | 
				
			||||||
 | 
					 * @param      update  set true to update buffer 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void code_input_set_result_callback(
 | 
				
			||||||
 | 
					    CodeInput* code_input,
 | 
				
			||||||
 | 
					    CodeInputOkCallback ok_callback,
 | 
				
			||||||
 | 
					    CodeInputFailCallback fail_callback,
 | 
				
			||||||
 | 
					    void* callback_context,
 | 
				
			||||||
 | 
					    uint8_t* buffer,
 | 
				
			||||||
 | 
					    uint8_t* buffer_length,
 | 
				
			||||||
 | 
					    bool update);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Set code input header text
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param      code_input  code input instance
 | 
				
			||||||
 | 
					 * @param      text        text to be shown
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void code_input_set_header_text(CodeInput* code_input, const char* text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Compare two buffers
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param      in       buffer to compare to source
 | 
				
			||||||
 | 
					 * @param      len_in   length of input buffer
 | 
				
			||||||
 | 
					 * @param      src      source buffer
 | 
				
			||||||
 | 
					 * @param      len_src  length of insourceput buffer
 | 
				
			||||||
 | 
					 * @return     true if buffers match
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool code_input_compare(uint8_t* in, size_t len_in, uint8_t* src, size_t len_src);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Push input into the end of array
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param      buffer   buffer
 | 
				
			||||||
 | 
					 * @param      length   length of buffer
 | 
				
			||||||
 | 
					 * @param      key      input key
 | 
				
			||||||
 | 
					 * @return     new length of input buffer
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					size_t code_input_push(uint8_t* buffer, size_t length, InputKey key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
@ -146,6 +146,21 @@ void widget_add_string_element(
 | 
				
			|||||||
    widget_add_element(widget, string_element);
 | 
					    widget_add_element(widget, string_element);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void widget_add_text_box_element(
 | 
				
			||||||
 | 
					    Widget* widget,
 | 
				
			||||||
 | 
					    uint8_t x,
 | 
				
			||||||
 | 
					    uint8_t y,
 | 
				
			||||||
 | 
					    uint8_t width,
 | 
				
			||||||
 | 
					    uint8_t height,
 | 
				
			||||||
 | 
					    Align horizontal,
 | 
				
			||||||
 | 
					    Align vertical,
 | 
				
			||||||
 | 
					    const char* text) {
 | 
				
			||||||
 | 
					    furi_assert(widget);
 | 
				
			||||||
 | 
					    WidgetElement* text_box_element =
 | 
				
			||||||
 | 
					        widget_element_text_box_create(x, y, width, height, horizontal, vertical, text);
 | 
				
			||||||
 | 
					    widget_add_element(widget, text_box_element);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void widget_add_button_element(
 | 
					void widget_add_button_element(
 | 
				
			||||||
    Widget* widget,
 | 
					    Widget* widget,
 | 
				
			||||||
    GuiButtonType button_type,
 | 
					    GuiButtonType button_type,
 | 
				
			||||||
 | 
				
			|||||||
@ -75,6 +75,30 @@ void widget_add_string_element(
 | 
				
			|||||||
    Font font,
 | 
					    Font font,
 | 
				
			||||||
    const char* text);
 | 
					    const char* text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Add Text Box Element
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param      widget      Widget instance
 | 
				
			||||||
 | 
					 * @param      x           x coordinate
 | 
				
			||||||
 | 
					 * @param      y           y coordinate
 | 
				
			||||||
 | 
					 * @param      width       width to fit text
 | 
				
			||||||
 | 
					 * @param      height      height to fit text
 | 
				
			||||||
 | 
					 * @param      horizontal  Align instance
 | 
				
			||||||
 | 
					 * @param      vertical    Align instance
 | 
				
			||||||
 | 
					 * @param[in]  text        Formatted text. The following formats are available:
 | 
				
			||||||
 | 
					 *                          "\e#Bold text\e#" - bold font is used
 | 
				
			||||||
 | 
					 *                          "\e*Monospaced text\e*" - monospaced font is used
 | 
				
			||||||
 | 
					 *                          "\e#Inversed text\e#" - white text on black background
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void widget_add_text_box_element(
 | 
				
			||||||
 | 
					    Widget* widget,
 | 
				
			||||||
 | 
					    uint8_t x,
 | 
				
			||||||
 | 
					    uint8_t y,
 | 
				
			||||||
 | 
					    uint8_t width,
 | 
				
			||||||
 | 
					    uint8_t height,
 | 
				
			||||||
 | 
					    Align horizontal,
 | 
				
			||||||
 | 
					    Align vertical,
 | 
				
			||||||
 | 
					    const char* text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Add Button Element
 | 
					/** Add Button Element
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @param      widget       Widget instance
 | 
					 * @param      widget       Widget instance
 | 
				
			||||||
 | 
				
			|||||||
@ -52,6 +52,16 @@ WidgetElement* widget_element_string_create(
 | 
				
			|||||||
    Font font,
 | 
					    Font font,
 | 
				
			||||||
    const char* text);
 | 
					    const char* text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Create text box element */
 | 
				
			||||||
 | 
					WidgetElement* widget_element_text_box_create(
 | 
				
			||||||
 | 
					    uint8_t x,
 | 
				
			||||||
 | 
					    uint8_t y,
 | 
				
			||||||
 | 
					    uint8_t width,
 | 
				
			||||||
 | 
					    uint8_t height,
 | 
				
			||||||
 | 
					    Align horizontal,
 | 
				
			||||||
 | 
					    Align vertical,
 | 
				
			||||||
 | 
					    const char* text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Create button element */
 | 
					/** Create button element */
 | 
				
			||||||
WidgetElement* widget_element_button_create(
 | 
					WidgetElement* widget_element_button_create(
 | 
				
			||||||
    GuiButtonType button_type,
 | 
					    GuiButtonType button_type,
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					#include "widget_element_i.h"
 | 
				
			||||||
 | 
					#include <m-string.h>
 | 
				
			||||||
 | 
					#include <gui/elements.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    uint8_t x;
 | 
				
			||||||
 | 
					    uint8_t y;
 | 
				
			||||||
 | 
					    uint8_t width;
 | 
				
			||||||
 | 
					    uint8_t height;
 | 
				
			||||||
 | 
					    Align horizontal;
 | 
				
			||||||
 | 
					    Align vertical;
 | 
				
			||||||
 | 
					    string_t text;
 | 
				
			||||||
 | 
					} GuiTextBoxModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void gui_text_box_draw(Canvas* canvas, WidgetElement* element) {
 | 
				
			||||||
 | 
					    furi_assert(canvas);
 | 
				
			||||||
 | 
					    furi_assert(element);
 | 
				
			||||||
 | 
					    GuiTextBoxModel* model = element->model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(string_size(model->text)) {
 | 
				
			||||||
 | 
					        elements_text_box(
 | 
				
			||||||
 | 
					            canvas,
 | 
				
			||||||
 | 
					            model->x,
 | 
				
			||||||
 | 
					            model->y,
 | 
				
			||||||
 | 
					            model->width,
 | 
				
			||||||
 | 
					            model->height,
 | 
				
			||||||
 | 
					            model->horizontal,
 | 
				
			||||||
 | 
					            model->vertical,
 | 
				
			||||||
 | 
					            string_get_cstr(model->text));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void gui_text_box_free(WidgetElement* gui_string) {
 | 
				
			||||||
 | 
					    furi_assert(gui_string);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    GuiTextBoxModel* model = gui_string->model;
 | 
				
			||||||
 | 
					    string_clear(model->text);
 | 
				
			||||||
 | 
					    free(gui_string->model);
 | 
				
			||||||
 | 
					    free(gui_string);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WidgetElement* widget_element_text_box_create(
 | 
				
			||||||
 | 
					    uint8_t x,
 | 
				
			||||||
 | 
					    uint8_t y,
 | 
				
			||||||
 | 
					    uint8_t width,
 | 
				
			||||||
 | 
					    uint8_t height,
 | 
				
			||||||
 | 
					    Align horizontal,
 | 
				
			||||||
 | 
					    Align vertical,
 | 
				
			||||||
 | 
					    const char* text) {
 | 
				
			||||||
 | 
					    furi_assert(text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Allocate and init model
 | 
				
			||||||
 | 
					    GuiTextBoxModel* model = furi_alloc(sizeof(GuiTextBoxModel));
 | 
				
			||||||
 | 
					    model->x = x;
 | 
				
			||||||
 | 
					    model->y = y;
 | 
				
			||||||
 | 
					    model->width = width;
 | 
				
			||||||
 | 
					    model->height = height;
 | 
				
			||||||
 | 
					    model->horizontal = horizontal;
 | 
				
			||||||
 | 
					    model->vertical = vertical;
 | 
				
			||||||
 | 
					    string_init_set_str(model->text, text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Allocate and init Element
 | 
				
			||||||
 | 
					    WidgetElement* gui_string = furi_alloc(sizeof(WidgetElement));
 | 
				
			||||||
 | 
					    gui_string->parent = NULL;
 | 
				
			||||||
 | 
					    gui_string->input = NULL;
 | 
				
			||||||
 | 
					    gui_string->draw = gui_text_box_draw;
 | 
				
			||||||
 | 
					    gui_string->free = gui_text_box_free;
 | 
				
			||||||
 | 
					    gui_string->model = model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return gui_string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -12,8 +12,8 @@ void nfc_scene_delete_on_enter(void* context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Setup Custom Widget view
 | 
					    // Setup Custom Widget view
 | 
				
			||||||
    char delete_str[64];
 | 
					    char delete_str[64];
 | 
				
			||||||
    snprintf(delete_str, sizeof(delete_str), "Delete %s", nfc->dev.dev_name);
 | 
					    snprintf(delete_str, sizeof(delete_str), "\e#Delete %s\e#", nfc->dev.dev_name);
 | 
				
			||||||
    widget_add_string_element(nfc->widget, 64, 6, AlignCenter, AlignTop, FontPrimary, delete_str);
 | 
					    widget_add_text_box_element(nfc->widget, 0, 0, 128, 24, AlignCenter, AlignCenter, delete_str);
 | 
				
			||||||
    widget_add_button_element(
 | 
					    widget_add_button_element(
 | 
				
			||||||
        nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_delete_widget_callback, nfc);
 | 
					        nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_delete_widget_callback, nfc);
 | 
				
			||||||
    widget_add_button_element(
 | 
					    widget_add_button_element(
 | 
				
			||||||
 | 
				
			|||||||
@ -35,8 +35,8 @@ void nfc_scene_device_info_on_enter(void* context) {
 | 
				
			|||||||
    Nfc* nfc = context;
 | 
					    Nfc* nfc = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Setup Custom Widget view
 | 
					    // Setup Custom Widget view
 | 
				
			||||||
    widget_add_string_element(
 | 
					    widget_add_text_box_element(
 | 
				
			||||||
        nfc->widget, 64, 6, AlignCenter, AlignTop, FontSecondary, nfc->dev.dev_name);
 | 
					        nfc->widget, 0, 0, 128, 24, AlignCenter, AlignCenter, nfc->dev.dev_name);
 | 
				
			||||||
    widget_add_button_element(
 | 
					    widget_add_button_element(
 | 
				
			||||||
        nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_device_info_widget_callback, nfc);
 | 
					        nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_device_info_widget_callback, nfc);
 | 
				
			||||||
    widget_add_button_element(
 | 
					    widget_add_button_element(
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ void power_cli_factory_reset(Cli* cli, string_t args, void* context) {
 | 
				
			|||||||
    char c = cli_getc(cli);
 | 
					    char c = cli_getc(cli);
 | 
				
			||||||
    if(c == 'y' || c == 'Y') {
 | 
					    if(c == 'y' || c == 'Y') {
 | 
				
			||||||
        printf("Data will be wiped after reboot.\r\n");
 | 
					        printf("Data will be wiped after reboot.\r\n");
 | 
				
			||||||
        furi_hal_boot_set_flags(FuriHalBootFlagFactoryReset);
 | 
					        furi_hal_bootloader_set_flags(FuriHalBootloaderFlagFactoryReset);
 | 
				
			||||||
        power_reboot(PowerBootModeNormal);
 | 
					        power_reboot(PowerBootModeNormal);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        printf("Safe choice.\r\n");
 | 
					        printf("Safe choice.\r\n");
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
#include "power_i.h"
 | 
					#include "power_i.h"
 | 
				
			||||||
#include <furi.h>
 | 
					#include <furi.h>
 | 
				
			||||||
#include "furi-hal-power.h"
 | 
					#include "furi-hal-power.h"
 | 
				
			||||||
#include "furi-hal-boot.h"
 | 
					#include "furi-hal-bootloader.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void power_off(Power* power) {
 | 
					void power_off(Power* power) {
 | 
				
			||||||
    furi_hal_power_off();
 | 
					    furi_hal_power_off();
 | 
				
			||||||
@ -14,9 +14,9 @@ void power_off(Power* power) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void power_reboot(PowerBootMode mode) {
 | 
					void power_reboot(PowerBootMode mode) {
 | 
				
			||||||
    if(mode == PowerBootModeNormal) {
 | 
					    if(mode == PowerBootModeNormal) {
 | 
				
			||||||
        furi_hal_boot_set_mode(FuriHalBootModeNormal);
 | 
					        furi_hal_bootloader_set_mode(FuriHalBootloaderModeNormal);
 | 
				
			||||||
    } else if(mode == PowerBootModeDfu) {
 | 
					    } else if(mode == PowerBootModeDfu) {
 | 
				
			||||||
        furi_hal_boot_set_mode(FuriHalBootModeDFU);
 | 
					        furi_hal_bootloader_set_mode(FuriHalBootloaderModeDFU);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    furi_hal_power_reset();
 | 
					    furi_hal_power_reset();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,22 +1,20 @@
 | 
				
			|||||||
#include "cmsis_os.h"
 | 
					#include "rpc_i.h"
 | 
				
			||||||
#include "cmsis_os2.h"
 | 
					#include <pb.h>
 | 
				
			||||||
#include "flipper.pb.h"
 | 
					#include <pb_decode.h>
 | 
				
			||||||
#include "furi-hal-delay.h"
 | 
					#include <pb_encode.h>
 | 
				
			||||||
#include "furi/check.h"
 | 
					#include <status.pb.h>
 | 
				
			||||||
#include "furi/log.h"
 | 
					#include <storage.pb.h>
 | 
				
			||||||
#include <m-string.h>
 | 
					#include <flipper.pb.h>
 | 
				
			||||||
#include "pb.h"
 | 
					#include <cmsis_os.h>
 | 
				
			||||||
#include "pb_decode.h"
 | 
					#include <cmsis_os2.h>
 | 
				
			||||||
#include "pb_encode.h"
 | 
					#include <portmacro.h>
 | 
				
			||||||
#include "portmacro.h"
 | 
					#include <furi.h>
 | 
				
			||||||
#include "status.pb.h"
 | 
					#include <cli/cli.h>
 | 
				
			||||||
#include "storage.pb.h"
 | 
					 | 
				
			||||||
#include <stdint.h>
 | 
					#include <stdint.h>
 | 
				
			||||||
#include <stdio.h>
 | 
					#include <stdio.h>
 | 
				
			||||||
#include <furi.h>
 | 
					 | 
				
			||||||
#include <stream_buffer.h>
 | 
					#include <stream_buffer.h>
 | 
				
			||||||
 | 
					#include <m-string.h>
 | 
				
			||||||
#include <m-dict.h>
 | 
					#include <m-dict.h>
 | 
				
			||||||
#include "rpc_i.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define RPC_TAG "RPC"
 | 
					#define RPC_TAG "RPC"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -51,10 +49,11 @@ static RpcSystemCallbacks rpc_systems[] = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
struct RpcSession {
 | 
					struct RpcSession {
 | 
				
			||||||
    RpcSendBytesCallback send_bytes_callback;
 | 
					    RpcSendBytesCallback send_bytes_callback;
 | 
				
			||||||
    void* send_bytes_context;
 | 
					    RpcSessionClosedCallback closed_callback;
 | 
				
			||||||
    osMutexId_t send_bytes_mutex;
 | 
					    void* context;
 | 
				
			||||||
 | 
					    osMutexId_t callbacks_mutex;
 | 
				
			||||||
    Rpc* rpc;
 | 
					    Rpc* rpc;
 | 
				
			||||||
    bool terminate_session;
 | 
					    bool terminate;
 | 
				
			||||||
    void** system_contexts;
 | 
					    void** system_contexts;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -70,6 +69,20 @@ struct Rpc {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static bool content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg);
 | 
					static bool content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void rpc_close_session_process(const PB_Main* msg_request, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(msg_request);
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Rpc* rpc = context;
 | 
				
			||||||
 | 
					    rpc_send_and_release_empty(rpc, msg_request->command_id, PB_CommandStatus_OK);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    osMutexAcquire(rpc->session.callbacks_mutex, osWaitForever);
 | 
				
			||||||
 | 
					    if(rpc->session.closed_callback) {
 | 
				
			||||||
 | 
					        rpc->session.closed_callback(rpc->session.context);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    osMutexRelease(rpc->session.callbacks_mutex);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static size_t rpc_sprintf_msg_file(
 | 
					static size_t rpc_sprintf_msg_file(
 | 
				
			||||||
    string_t str,
 | 
					    string_t str,
 | 
				
			||||||
    const char* prefix,
 | 
					    const char* prefix,
 | 
				
			||||||
@ -105,6 +118,31 @@ static size_t rpc_sprintf_msg_file(
 | 
				
			|||||||
    return cnt;
 | 
					    return cnt;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void rpc_print_data(const char* prefix, uint8_t* buffer, size_t size) {
 | 
				
			||||||
 | 
					    string_t str;
 | 
				
			||||||
 | 
					    string_init(str);
 | 
				
			||||||
 | 
					    string_reserve(str, 100 + size * 5);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    string_cat_printf(str, "\r\n%s DEC(%d): {", prefix, size);
 | 
				
			||||||
 | 
					    for(int i = 0; i < size; ++i) {
 | 
				
			||||||
 | 
					        string_cat_printf(str, "%d, ", buffer[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    string_cat_printf(str, "}\r\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    printf("%s", string_get_cstr(str));
 | 
				
			||||||
 | 
					    string_clean(str);
 | 
				
			||||||
 | 
					    string_reserve(str, 100 + size * 3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    string_cat_printf(str, "%s HEX(%d): {", prefix, size);
 | 
				
			||||||
 | 
					    for(int i = 0; i < size; ++i) {
 | 
				
			||||||
 | 
					        string_cat_printf(str, "%02X", buffer[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    string_cat_printf(str, "}\r\n\r\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    printf("%s", string_get_cstr(str));
 | 
				
			||||||
 | 
					    string_clear(str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void rpc_print_message(const PB_Main* message) {
 | 
					void rpc_print_message(const PB_Main* message) {
 | 
				
			||||||
    string_t str;
 | 
					    string_t str;
 | 
				
			||||||
    string_init(str);
 | 
					    string_init(str);
 | 
				
			||||||
@ -120,6 +158,9 @@ void rpc_print_message(const PB_Main* message) {
 | 
				
			|||||||
        /* not implemented yet */
 | 
					        /* not implemented yet */
 | 
				
			||||||
        string_cat_printf(str, "\tNOT_IMPLEMENTED (%d) {\r\n", message->which_content);
 | 
					        string_cat_printf(str, "\tNOT_IMPLEMENTED (%d) {\r\n", message->which_content);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					    case PB_Main_stop_session_tag:
 | 
				
			||||||
 | 
					        string_cat_printf(str, "\tstop_session {\r\n");
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
    case PB_Main_app_start_tag: {
 | 
					    case PB_Main_app_start_tag: {
 | 
				
			||||||
        string_cat_printf(str, "\tapp_start {\r\n");
 | 
					        string_cat_printf(str, "\tapp_start {\r\n");
 | 
				
			||||||
        const char* name = message->content.app_start.name;
 | 
					        const char* name = message->content.app_start.name;
 | 
				
			||||||
@ -242,7 +283,7 @@ static Rpc* rpc_alloc(void) {
 | 
				
			|||||||
    return rpc;
 | 
					    return rpc;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RpcSession* rpc_open_session(Rpc* rpc) {
 | 
					RpcSession* rpc_session_open(Rpc* rpc) {
 | 
				
			||||||
    furi_assert(rpc);
 | 
					    furi_assert(rpc);
 | 
				
			||||||
    bool result = false;
 | 
					    bool result = false;
 | 
				
			||||||
    furi_check(osMutexAcquire(rpc->busy_mutex, osWaitForever) == osOK);
 | 
					    furi_check(osMutexAcquire(rpc->busy_mutex, osWaitForever) == osOK);
 | 
				
			||||||
@ -256,41 +297,94 @@ RpcSession* rpc_open_session(Rpc* rpc) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if(result) {
 | 
					    if(result) {
 | 
				
			||||||
        RpcSession* session = &rpc->session;
 | 
					        RpcSession* session = &rpc->session;
 | 
				
			||||||
        session->send_bytes_mutex = osMutexNew(NULL);
 | 
					        session->callbacks_mutex = osMutexNew(NULL);
 | 
				
			||||||
        session->rpc = rpc;
 | 
					        session->rpc = rpc;
 | 
				
			||||||
        session->terminate_session = false;
 | 
					        session->terminate = false;
 | 
				
			||||||
 | 
					        xStreamBufferReset(rpc->stream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        session->system_contexts = furi_alloc(COUNT_OF(rpc_systems) * sizeof(void*));
 | 
					        session->system_contexts = furi_alloc(COUNT_OF(rpc_systems) * sizeof(void*));
 | 
				
			||||||
        for(int i = 0; i < COUNT_OF(rpc_systems); ++i) {
 | 
					        for(int i = 0; i < COUNT_OF(rpc_systems); ++i) {
 | 
				
			||||||
            session->system_contexts[i] = rpc_systems[i].alloc(rpc);
 | 
					            session->system_contexts[i] = rpc_systems[i].alloc(rpc);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        RpcHandler rpc_handler = {
 | 
				
			||||||
 | 
					            .message_handler = rpc_close_session_process,
 | 
				
			||||||
 | 
					            .decode_submessage = NULL,
 | 
				
			||||||
 | 
					            .context = rpc,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        rpc_add_handler(rpc, PB_Main_stop_session_tag, &rpc_handler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        FURI_LOG_D(RPC_TAG, "Session started\r\n");
 | 
					        FURI_LOG_D(RPC_TAG, "Session started\r\n");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return result ? &rpc->session : NULL; /* support 1 open session for now */
 | 
					    return result ? &rpc->session : NULL; /* support 1 open session for now */
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void rpc_close_session(RpcSession* session) {
 | 
					void rpc_session_close(RpcSession* session) {
 | 
				
			||||||
    furi_assert(session);
 | 
					    furi_assert(session);
 | 
				
			||||||
    furi_assert(session->rpc);
 | 
					    furi_assert(session->rpc);
 | 
				
			||||||
    furi_assert(session->rpc->busy);
 | 
					    furi_assert(session->rpc->busy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    rpc_set_send_bytes_callback(session, NULL, NULL);
 | 
					    rpc_session_set_send_bytes_callback(session, NULL);
 | 
				
			||||||
 | 
					    rpc_session_set_close_callback(session, NULL);
 | 
				
			||||||
    osEventFlagsSet(session->rpc->events, RPC_EVENT_DISCONNECT);
 | 
					    osEventFlagsSet(session->rpc->events, RPC_EVENT_DISCONNECT);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void rpc_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback, void* context) {
 | 
					static void rpc_free_session(RpcSession* session) {
 | 
				
			||||||
 | 
					    furi_assert(session);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(int i = 0; i < COUNT_OF(rpc_systems); ++i) {
 | 
				
			||||||
 | 
					        if(rpc_systems[i].free) {
 | 
				
			||||||
 | 
					            rpc_systems[i].free(session->system_contexts[i]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    free(session->system_contexts);
 | 
				
			||||||
 | 
					    osMutexDelete(session->callbacks_mutex);
 | 
				
			||||||
 | 
					    RpcHandlerDict_clean(session->rpc->handlers);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    session->context = NULL;
 | 
				
			||||||
 | 
					    session->closed_callback = NULL;
 | 
				
			||||||
 | 
					    session->send_bytes_callback = NULL;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void rpc_session_set_context(RpcSession* session, void* context) {
 | 
				
			||||||
    furi_assert(session);
 | 
					    furi_assert(session);
 | 
				
			||||||
    furi_assert(session->rpc);
 | 
					    furi_assert(session->rpc);
 | 
				
			||||||
    furi_assert(session->rpc->busy);
 | 
					    furi_assert(session->rpc->busy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    osMutexAcquire(session->send_bytes_mutex, osWaitForever);
 | 
					    osMutexAcquire(session->callbacks_mutex, osWaitForever);
 | 
				
			||||||
    session->send_bytes_callback = callback;
 | 
					    session->context = context;
 | 
				
			||||||
    session->send_bytes_context = context;
 | 
					    osMutexRelease(session->callbacks_mutex);
 | 
				
			||||||
    osMutexRelease(session->send_bytes_mutex);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void rpc_session_set_close_callback(RpcSession* session, RpcSessionClosedCallback callback) {
 | 
				
			||||||
 | 
					    furi_assert(session);
 | 
				
			||||||
 | 
					    furi_assert(session->rpc);
 | 
				
			||||||
 | 
					    furi_assert(session->rpc->busy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    osMutexAcquire(session->callbacks_mutex, osWaitForever);
 | 
				
			||||||
 | 
					    session->closed_callback = callback;
 | 
				
			||||||
 | 
					    osMutexRelease(session->callbacks_mutex);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void rpc_session_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback) {
 | 
				
			||||||
 | 
					    furi_assert(session);
 | 
				
			||||||
 | 
					    furi_assert(session->rpc);
 | 
				
			||||||
 | 
					    furi_assert(session->rpc->busy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    osMutexAcquire(session->callbacks_mutex, osWaitForever);
 | 
				
			||||||
 | 
					    session->send_bytes_callback = callback;
 | 
				
			||||||
 | 
					    osMutexRelease(session->callbacks_mutex);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Doesn't forbid using rpc_feed_bytes() after session close - it's safe.
 | 
				
			||||||
 | 
					 * Because any bytes received in buffer will be flushed before next session.
 | 
				
			||||||
 | 
					 * If bytes get into stream buffer before it's get epmtified and this
 | 
				
			||||||
 | 
					 * command is gets processed - it's safe either. But case of it is quite
 | 
				
			||||||
 | 
					 * odd: client sends close request and sends command after.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
size_t
 | 
					size_t
 | 
				
			||||||
    rpc_feed_bytes(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout) {
 | 
					    rpc_session_feed(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout) {
 | 
				
			||||||
    furi_assert(session);
 | 
					    furi_assert(session);
 | 
				
			||||||
    Rpc* rpc = session->rpc;
 | 
					    Rpc* rpc = session->rpc;
 | 
				
			||||||
    furi_assert(rpc->busy);
 | 
					    furi_assert(rpc->busy);
 | 
				
			||||||
@ -306,6 +400,8 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
 | 
				
			|||||||
    uint32_t flags = 0;
 | 
					    uint32_t flags = 0;
 | 
				
			||||||
    size_t bytes_received = 0;
 | 
					    size_t bytes_received = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_assert(istream->bytes_left);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while(1) {
 | 
					    while(1) {
 | 
				
			||||||
        bytes_received +=
 | 
					        bytes_received +=
 | 
				
			||||||
            xStreamBufferReceive(rpc->stream, buf + bytes_received, count - bytes_received, 0);
 | 
					            xStreamBufferReceive(rpc->stream, buf + bytes_received, count - bytes_received, 0);
 | 
				
			||||||
@ -315,7 +411,9 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
 | 
				
			|||||||
            flags = osEventFlagsWait(rpc->events, RPC_EVENTS_ALL, 0, osWaitForever);
 | 
					            flags = osEventFlagsWait(rpc->events, RPC_EVENTS_ALL, 0, osWaitForever);
 | 
				
			||||||
            if(flags & RPC_EVENT_DISCONNECT) {
 | 
					            if(flags & RPC_EVENT_DISCONNECT) {
 | 
				
			||||||
                if(xStreamBufferIsEmpty(rpc->stream)) {
 | 
					                if(xStreamBufferIsEmpty(rpc->stream)) {
 | 
				
			||||||
                    rpc->session.terminate_session = true;
 | 
					                    rpc->session.terminate = true;
 | 
				
			||||||
 | 
					                    istream->bytes_left = 0;
 | 
				
			||||||
 | 
					                    bytes_received = 0;
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    /* Save disconnect flag and continue reading buffer */
 | 
					                    /* Save disconnect flag and continue reading buffer */
 | 
				
			||||||
@ -325,61 +423,44 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if DEBUG_PRINT
 | 
				
			||||||
 | 
					    rpc_print_data("INPUT", buf, bytes_received);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (count == bytes_received);
 | 
					    return (count == bytes_received);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void rpc_encode_and_send(Rpc* rpc, PB_Main* main_message) {
 | 
					void rpc_send_and_release(Rpc* rpc, PB_Main* message) {
 | 
				
			||||||
    furi_assert(rpc);
 | 
					    furi_assert(rpc);
 | 
				
			||||||
    furi_assert(main_message);
 | 
					    furi_assert(message);
 | 
				
			||||||
    RpcSession* session = &rpc->session;
 | 
					    RpcSession* session = &rpc->session;
 | 
				
			||||||
    pb_ostream_t ostream = PB_OSTREAM_SIZING;
 | 
					    pb_ostream_t ostream = PB_OSTREAM_SIZING;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if DEBUG_PRINT
 | 
					#if DEBUG_PRINT
 | 
				
			||||||
    FURI_LOG_I(RPC_TAG, "OUTPUT:");
 | 
					    FURI_LOG_I(RPC_TAG, "OUTPUT:");
 | 
				
			||||||
    rpc_print_message(main_message);
 | 
					    rpc_print_message(message);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool result = pb_encode_ex(&ostream, &PB_Main_msg, main_message, PB_ENCODE_DELIMITED);
 | 
					    bool result = pb_encode_ex(&ostream, &PB_Main_msg, message, PB_ENCODE_DELIMITED);
 | 
				
			||||||
    furi_check(result && ostream.bytes_written);
 | 
					    furi_check(result && ostream.bytes_written);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    uint8_t* buffer = furi_alloc(ostream.bytes_written);
 | 
					    uint8_t* buffer = furi_alloc(ostream.bytes_written);
 | 
				
			||||||
    ostream = pb_ostream_from_buffer(buffer, ostream.bytes_written);
 | 
					    ostream = pb_ostream_from_buffer(buffer, ostream.bytes_written);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pb_encode_ex(&ostream, &PB_Main_msg, main_message, PB_ENCODE_DELIMITED);
 | 
					    pb_encode_ex(&ostream, &PB_Main_msg, message, PB_ENCODE_DELIMITED);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
#if DEBUG_PRINT
 | 
					#if DEBUG_PRINT
 | 
				
			||||||
        string_t str;
 | 
					    rpc_print_data("OUTPUT", buffer, ostream.bytes_written);
 | 
				
			||||||
        string_init(str);
 | 
					#endif
 | 
				
			||||||
        string_reserve(str, 100 + ostream.bytes_written * 5);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        string_cat_printf(str, "\r\nREPONSE DEC(%d): {", ostream.bytes_written);
 | 
					    osMutexAcquire(session->callbacks_mutex, osWaitForever);
 | 
				
			||||||
        for(int i = 0; i < ostream.bytes_written; ++i) {
 | 
					    if(session->send_bytes_callback) {
 | 
				
			||||||
            string_cat_printf(str, "%d, ", buffer[i]);
 | 
					        session->send_bytes_callback(session->context, buffer, ostream.bytes_written);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        string_cat_printf(str, "}\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        printf("%s", string_get_cstr(str));
 | 
					 | 
				
			||||||
        string_clean(str);
 | 
					 | 
				
			||||||
        string_reserve(str, 100 + ostream.bytes_written * 3);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        string_cat_printf(str, "REPONSE HEX(%d): {", ostream.bytes_written);
 | 
					 | 
				
			||||||
        for(int i = 0; i < ostream.bytes_written; ++i) {
 | 
					 | 
				
			||||||
            string_cat_printf(str, "%02X", buffer[i]);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        string_cat_printf(str, "}\r\n\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        printf("%s", string_get_cstr(str));
 | 
					 | 
				
			||||||
#endif // DEBUG_PRINT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        osMutexAcquire(session->send_bytes_mutex, osWaitForever);
 | 
					 | 
				
			||||||
        if(session->send_bytes_callback) {
 | 
					 | 
				
			||||||
            session->send_bytes_callback(
 | 
					 | 
				
			||||||
                session->send_bytes_context, buffer, ostream.bytes_written);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        osMutexRelease(session->send_bytes_mutex);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    osMutexRelease(session->callbacks_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    free(buffer);
 | 
					    free(buffer);
 | 
				
			||||||
 | 
					    pb_release(&PB_Main_msg, message);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg) {
 | 
					static bool content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg) {
 | 
				
			||||||
@ -399,12 +480,17 @@ int32_t rpc_srv(void* p) {
 | 
				
			|||||||
    Rpc* rpc = rpc_alloc();
 | 
					    Rpc* rpc = rpc_alloc();
 | 
				
			||||||
    furi_record_create("rpc", rpc);
 | 
					    furi_record_create("rpc", rpc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Cli* cli = furi_record_open("cli");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cli_add_command(
 | 
				
			||||||
 | 
					        cli, "start_rpc_session", CliCommandFlagParallelSafe, rpc_cli_command_start_session, rpc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while(1) {
 | 
					    while(1) {
 | 
				
			||||||
        pb_istream_t istream = {
 | 
					        pb_istream_t istream = {
 | 
				
			||||||
            .callback = rpc_pb_stream_read,
 | 
					            .callback = rpc_pb_stream_read,
 | 
				
			||||||
            .state = rpc,
 | 
					            .state = rpc,
 | 
				
			||||||
            .errmsg = NULL,
 | 
					            .errmsg = NULL,
 | 
				
			||||||
            .bytes_left = 0x7FFFFFFF,
 | 
					            .bytes_left = 1024, /* max incoming message size */
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(pb_decode_ex(&istream, &PB_Main_msg, rpc->decoded_message, PB_DECODE_DELIMITED)) {
 | 
					        if(pb_decode_ex(&istream, &PB_Main_msg, rpc->decoded_message, PB_DECODE_DELIMITED)) {
 | 
				
			||||||
@ -417,35 +503,25 @@ int32_t rpc_srv(void* p) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if(handler && handler->message_handler) {
 | 
					            if(handler && handler->message_handler) {
 | 
				
			||||||
                handler->message_handler(rpc->decoded_message, handler->context);
 | 
					                handler->message_handler(rpc->decoded_message, handler->context);
 | 
				
			||||||
            } else if(!handler) {
 | 
					            } else if(!handler && !rpc->session.terminate) {
 | 
				
			||||||
                FURI_LOG_E(
 | 
					                FURI_LOG_E(
 | 
				
			||||||
                    RPC_TAG,
 | 
					                    RPC_TAG, "Unhandled message, tag: %d", rpc->decoded_message->which_content);
 | 
				
			||||||
                    "Unhandled message, tag: %d\r\n",
 | 
					 | 
				
			||||||
                    rpc->decoded_message->which_content);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            pb_release(&PB_Main_msg, rpc->decoded_message);
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            pb_release(&PB_Main_msg, rpc->decoded_message);
 | 
					            xStreamBufferReset(rpc->stream);
 | 
				
			||||||
            RpcSession* session = &rpc->session;
 | 
					            if(!rpc->session.terminate) {
 | 
				
			||||||
            if(session->terminate_session) {
 | 
					                FURI_LOG_E(RPC_TAG, "Decode failed, error: \'%.128s\'", PB_GET_ERROR(&istream));
 | 
				
			||||||
                session->terminate_session = false;
 | 
					 | 
				
			||||||
                osEventFlagsClear(rpc->events, RPC_EVENTS_ALL);
 | 
					 | 
				
			||||||
                FURI_LOG_D(RPC_TAG, "Session terminated\r\n");
 | 
					 | 
				
			||||||
                for(int i = 0; i < COUNT_OF(rpc_systems); ++i) {
 | 
					 | 
				
			||||||
                    if(rpc_systems[i].free) {
 | 
					 | 
				
			||||||
                        rpc_systems[i].free(session->system_contexts[i]);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                free(session->system_contexts);
 | 
					 | 
				
			||||||
                osMutexDelete(session->send_bytes_mutex);
 | 
					 | 
				
			||||||
                RpcHandlerDict_clean(rpc->handlers);
 | 
					 | 
				
			||||||
                rpc->busy = false;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                xStreamBufferReset(rpc->stream);
 | 
					 | 
				
			||||||
                FURI_LOG_E(
 | 
					 | 
				
			||||||
                    RPC_TAG, "Decode failed, error: \'%.128s\'\r\n", PB_GET_ERROR(&istream));
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pb_release(&PB_Main_msg, rpc->decoded_message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(rpc->session.terminate) {
 | 
				
			||||||
 | 
					            FURI_LOG_D(RPC_TAG, "Session terminated");
 | 
				
			||||||
 | 
					            osEventFlagsClear(rpc->events, RPC_EVENTS_ALL);
 | 
				
			||||||
 | 
					            rpc_free_session(&rpc->session);
 | 
				
			||||||
 | 
					            rpc->busy = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -456,13 +532,13 @@ void rpc_add_handler(Rpc* rpc, pb_size_t message_tag, RpcHandler* handler) {
 | 
				
			|||||||
    RpcHandlerDict_set_at(rpc->handlers, message_tag, *handler);
 | 
					    RpcHandlerDict_set_at(rpc->handlers, message_tag, *handler);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void rpc_encode_and_send_empty(Rpc* rpc, uint32_t command_id, PB_CommandStatus status) {
 | 
					void rpc_send_and_release_empty(Rpc* rpc, uint32_t command_id, PB_CommandStatus status) {
 | 
				
			||||||
    PB_Main message = {
 | 
					    PB_Main message = {
 | 
				
			||||||
        .command_id = command_id,
 | 
					        .command_id = command_id,
 | 
				
			||||||
        .command_status = status,
 | 
					        .command_status = status,
 | 
				
			||||||
        .has_next = false,
 | 
					        .has_next = false,
 | 
				
			||||||
        .which_content = PB_Main_empty_tag,
 | 
					        .which_content = PB_Main_empty_tag,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    rpc_encode_and_send(rpc, &message);
 | 
					    rpc_send_and_release(rpc, &message);
 | 
				
			||||||
    pb_release(&PB_Main_msg, &message);
 | 
					    pb_release(&PB_Main_msg, &message);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,79 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
#include <stddef.h>
 | 
					#include <stddef.h>
 | 
				
			||||||
#include <stdint.h>
 | 
					#include <stdint.h>
 | 
				
			||||||
 | 
					#include <stdbool.h>
 | 
				
			||||||
#include "cmsis_os.h"
 | 
					#include "cmsis_os.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Rpc interface. Used for opening session only. */
 | 
				
			||||||
typedef struct Rpc Rpc;
 | 
					typedef struct Rpc Rpc;
 | 
				
			||||||
 | 
					/** Rpc session interface */
 | 
				
			||||||
typedef struct RpcSession RpcSession;
 | 
					typedef struct RpcSession RpcSession;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Callback to send to client any data (e.g. response to command) */
 | 
				
			||||||
typedef void (*RpcSendBytesCallback)(void* context, uint8_t* bytes, size_t bytes_len);
 | 
					typedef void (*RpcSendBytesCallback)(void* context, uint8_t* bytes, size_t bytes_len);
 | 
				
			||||||
 | 
					/** Callback to notify transport layer that close_session command
 | 
				
			||||||
 | 
					 * is received. Any other actions lays on transport layer.
 | 
				
			||||||
 | 
					 * No destruction or session close preformed. */
 | 
				
			||||||
 | 
					typedef void (*RpcSessionClosedCallback)(void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RpcSession* rpc_open_session(Rpc* rpc);
 | 
					/** Open RPC session
 | 
				
			||||||
void rpc_close_session(RpcSession* session);
 | 
					 *
 | 
				
			||||||
/* WARN: can't call RPC API within RpcSendBytesCallback */
 | 
					 * USAGE:
 | 
				
			||||||
void rpc_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback, void* context);
 | 
					 * 1) rpc_session_open();
 | 
				
			||||||
size_t
 | 
					 * 2) rpc_session_set_context();
 | 
				
			||||||
    rpc_feed_bytes(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout);
 | 
					 * 3) rpc_session_set_send_bytes_callback();
 | 
				
			||||||
 | 
					 * 4) rpc_session_set_close_callback();
 | 
				
			||||||
 | 
					 * 5) while(1) {
 | 
				
			||||||
 | 
					 *      rpc_session_feed();
 | 
				
			||||||
 | 
					 *    }
 | 
				
			||||||
 | 
					 * 6) rpc_session_close();
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param   rpc     instance
 | 
				
			||||||
 | 
					 * @return          pointer to RpcSession descriptor, or
 | 
				
			||||||
 | 
					 *                  NULL if RPC is busy and can't open session now
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					RpcSession* rpc_session_open(Rpc* rpc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Close RPC session
 | 
				
			||||||
 | 
					 * It is guaranteed that no callbacks will be called
 | 
				
			||||||
 | 
					 * as soon as session is closed. So no need in setting
 | 
				
			||||||
 | 
					 * callbacks to NULL after session close.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param   session     pointer to RpcSession descriptor
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void rpc_session_close(RpcSession* session);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Set session context for callbacks to pass
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param   session     pointer to RpcSession descriptor
 | 
				
			||||||
 | 
					 * @param   context     context to pass to callbacks
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void rpc_session_set_context(RpcSession* session, void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Set callback to send bytes to client
 | 
				
			||||||
 | 
					 *  WARN: It's forbidden to call RPC API within RpcSendBytesCallback
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param   session     pointer to RpcSession descriptor
 | 
				
			||||||
 | 
					 * @param   callback    callback to send bytes to client (can be NULL)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void rpc_session_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Set callback to be called when RPC command to close session is received
 | 
				
			||||||
 | 
					 *  WARN: It's forbidden to call RPC API within RpcSessionClosedCallback
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param   session     pointer to RpcSession descriptor
 | 
				
			||||||
 | 
					 * @param   callback    callback to inform about RPC close session command (can be NULL)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void rpc_session_set_close_callback(RpcSession* session, RpcSessionClosedCallback callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Give bytes to RPC service to decode them and perform command
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param   session     pointer to RpcSession descriptor
 | 
				
			||||||
 | 
					 * @param   buffer      buffer to provide to RPC service
 | 
				
			||||||
 | 
					 * @param   size        size of buffer
 | 
				
			||||||
 | 
					 * @param   timeout     max timeout to wait till all buffer will be consumed
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @return              actually consumed bytes
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					size_t rpc_session_feed(RpcSession* session, uint8_t* buffer, size_t size, TickType_t timeout);
 | 
				
			||||||
 | 
				
			|||||||
@ -34,7 +34,7 @@ void rpc_system_app_start_process(const PB_Main* request, void* context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    furi_record_close("loader");
 | 
					    furi_record_close("loader");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    rpc_encode_and_send_empty(rpc, request->command_id, result);
 | 
					    rpc_send_and_release_empty(rpc, request->command_id, result);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void rpc_system_app_lock_status_process(const PB_Main* request, void* context) {
 | 
					void rpc_system_app_lock_status_process(const PB_Main* request, void* context) {
 | 
				
			||||||
@ -56,7 +56,8 @@ void rpc_system_app_lock_status_process(const PB_Main* request, void* context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    furi_record_close("loader");
 | 
					    furi_record_close("loader");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    rpc_encode_and_send(rpc, &response);
 | 
					    rpc_send_and_release(rpc, &response);
 | 
				
			||||||
 | 
					    pb_release(&PB_Main_msg, &response);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void* rpc_system_app_alloc(Rpc* rpc) {
 | 
					void* rpc_system_app_alloc(Rpc* rpc) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										59
									
								
								applications/rpc/rpc_cli.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								applications/rpc/rpc_cli.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					#include <cli/cli.h>
 | 
				
			||||||
 | 
					#include <furi.h>
 | 
				
			||||||
 | 
					#include <rpc/rpc.h>
 | 
				
			||||||
 | 
					#include <furi-hal-vcp.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    Cli* cli;
 | 
				
			||||||
 | 
					    bool session_close_request;
 | 
				
			||||||
 | 
					} CliRpc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CLI_READ_BUFFER_SIZE 100
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    furi_assert(bytes);
 | 
				
			||||||
 | 
					    furi_assert(bytes_len);
 | 
				
			||||||
 | 
					    CliRpc* cli_rpc = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cli_write(cli_rpc->cli, bytes, bytes_len);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void rpc_session_close_callback(void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    CliRpc* cli_rpc = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cli_rpc->session_close_request = true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void rpc_cli_command_start_session(Cli* cli, string_t args, void* context) {
 | 
				
			||||||
 | 
					    Rpc* rpc = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    RpcSession* rpc_session = rpc_session_open(rpc);
 | 
				
			||||||
 | 
					    if(rpc_session == NULL) {
 | 
				
			||||||
 | 
					        printf("Another session is in progress\r\n");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    CliRpc cli_rpc = {.cli = cli, .session_close_request = false};
 | 
				
			||||||
 | 
					    rpc_session_set_context(rpc_session, &cli_rpc);
 | 
				
			||||||
 | 
					    rpc_session_set_send_bytes_callback(rpc_session, rpc_send_bytes_callback);
 | 
				
			||||||
 | 
					    rpc_session_set_close_callback(rpc_session, rpc_session_close_callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t* buffer = furi_alloc(CLI_READ_BUFFER_SIZE);
 | 
				
			||||||
 | 
					    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) {
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(size_received) {
 | 
				
			||||||
 | 
					            rpc_session_feed(rpc_session, buffer, size_received, 3000);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rpc_session_close(rpc_session);
 | 
				
			||||||
 | 
					    free(buffer);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,9 +1,10 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
#include "rpc.h"
 | 
					#include "rpc.h"
 | 
				
			||||||
#include "pb.h"
 | 
					#include <pb.h>
 | 
				
			||||||
#include "pb_decode.h"
 | 
					#include <pb_decode.h>
 | 
				
			||||||
#include "pb_encode.h"
 | 
					#include <pb_encode.h>
 | 
				
			||||||
#include "flipper.pb.h"
 | 
					#include <flipper.pb.h>
 | 
				
			||||||
 | 
					#include <cli/cli.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef void* (*RpcSystemAlloc)(Rpc*);
 | 
					typedef void* (*RpcSystemAlloc)(Rpc*);
 | 
				
			||||||
typedef void (*RpcSystemFree)(void*);
 | 
					typedef void (*RpcSystemFree)(void*);
 | 
				
			||||||
@ -15,8 +16,8 @@ typedef struct {
 | 
				
			|||||||
    void* context;
 | 
					    void* context;
 | 
				
			||||||
} RpcHandler;
 | 
					} RpcHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void rpc_encode_and_send(Rpc* rpc, PB_Main* main_message);
 | 
					void rpc_send_and_release(Rpc* rpc, PB_Main* main_message);
 | 
				
			||||||
void rpc_encode_and_send_empty(Rpc* rpc, uint32_t command_id, PB_CommandStatus status);
 | 
					void rpc_send_and_release_empty(Rpc* rpc, uint32_t command_id, PB_CommandStatus status);
 | 
				
			||||||
void rpc_add_handler(Rpc* rpc, pb_size_t message_tag, RpcHandler* handler);
 | 
					void rpc_add_handler(Rpc* rpc, pb_size_t message_tag, RpcHandler* handler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void* rpc_system_status_alloc(Rpc* rpc);
 | 
					void* rpc_system_status_alloc(Rpc* rpc);
 | 
				
			||||||
@ -25,3 +26,4 @@ void rpc_system_storage_free(void* ctx);
 | 
				
			|||||||
void* rpc_system_app_alloc(Rpc* rpc);
 | 
					void* rpc_system_app_alloc(Rpc* rpc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void rpc_print_message(const PB_Main* message);
 | 
					void rpc_print_message(const PB_Main* message);
 | 
				
			||||||
 | 
					void rpc_cli_command_start_session(Cli* cli, string_t args, void* context);
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,8 @@ void rpc_system_status_ping_process(const PB_Main* msg_request, void* context) {
 | 
				
			|||||||
    msg_response.command_id = msg_request->command_id;
 | 
					    msg_response.command_id = msg_request->command_id;
 | 
				
			||||||
    msg_response.which_content = PB_Main_ping_response_tag;
 | 
					    msg_response.which_content = PB_Main_ping_response_tag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    rpc_encode_and_send(context, &msg_response);
 | 
					    rpc_send_and_release(context, &msg_response);
 | 
				
			||||||
 | 
					    pb_release(&PB_Main_msg, &msg_response);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void* rpc_system_status_alloc(Rpc* rpc) {
 | 
					void* rpc_system_status_alloc(Rpc* rpc) {
 | 
				
			||||||
 | 
				
			|||||||
@ -35,7 +35,7 @@ static void rpc_system_storage_reset_state(RpcStorageSystem* rpc_storage, bool s
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if(rpc_storage->state != RpcStorageStateIdle) {
 | 
					    if(rpc_storage->state != RpcStorageStateIdle) {
 | 
				
			||||||
        if(send_error) {
 | 
					        if(send_error) {
 | 
				
			||||||
            rpc_encode_and_send_empty(
 | 
					            rpc_send_and_release_empty(
 | 
				
			||||||
                rpc_storage->rpc,
 | 
					                rpc_storage->rpc,
 | 
				
			||||||
                rpc_storage->current_command_id,
 | 
					                rpc_storage->current_command_id,
 | 
				
			||||||
                PB_CommandStatus_ERROR_CONTINUOUS_COMMAND_INTERRUPTED);
 | 
					                PB_CommandStatus_ERROR_CONTINUOUS_COMMAND_INTERRUPTED);
 | 
				
			||||||
@ -96,6 +96,31 @@ static PB_CommandStatus rpc_system_storage_get_file_error(File* file) {
 | 
				
			|||||||
    return rpc_system_storage_get_error(storage_file_get_error(file));
 | 
					    return rpc_system_storage_get_error(storage_file_get_error(file));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void rpc_system_storage_list_root(const PB_Main* request, void* context) {
 | 
				
			||||||
 | 
					    RpcStorageSystem* rpc_storage = context;
 | 
				
			||||||
 | 
					    const char* hard_coded_dirs[] = {"any", "int", "ext"};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    PB_Main response = {
 | 
				
			||||||
 | 
					        .has_next = false,
 | 
				
			||||||
 | 
					        .command_id = request->command_id,
 | 
				
			||||||
 | 
					        .command_status = PB_CommandStatus_OK,
 | 
				
			||||||
 | 
					        .which_content = PB_Main_storage_list_response_tag,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    furi_assert(COUNT_OF(hard_coded_dirs) < COUNT_OF(response.content.storage_list_response.file));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(int i = 0; i < COUNT_OF(hard_coded_dirs); ++i) {
 | 
				
			||||||
 | 
					        ++response.content.storage_list_response.file_count;
 | 
				
			||||||
 | 
					        response.content.storage_list_response.file[i].data = NULL;
 | 
				
			||||||
 | 
					        response.content.storage_list_response.file[i].size = 0;
 | 
				
			||||||
 | 
					        response.content.storage_list_response.file[i].type = PB_Storage_File_FileType_DIR;
 | 
				
			||||||
 | 
					        char* str = furi_alloc(strlen(hard_coded_dirs[i]) + 1);
 | 
				
			||||||
 | 
					        strcpy(str, hard_coded_dirs[i]);
 | 
				
			||||||
 | 
					        response.content.storage_list_response.file[i].name = str;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rpc_send_and_release(rpc_storage->rpc, &response);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void rpc_system_storage_list_process(const PB_Main* request, void* context) {
 | 
					static void rpc_system_storage_list_process(const PB_Main* request, void* context) {
 | 
				
			||||||
    furi_assert(request);
 | 
					    furi_assert(request);
 | 
				
			||||||
    furi_assert(context);
 | 
					    furi_assert(context);
 | 
				
			||||||
@ -104,6 +129,11 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
 | 
				
			|||||||
    RpcStorageSystem* rpc_storage = context;
 | 
					    RpcStorageSystem* rpc_storage = context;
 | 
				
			||||||
    rpc_system_storage_reset_state(rpc_storage, true);
 | 
					    rpc_system_storage_reset_state(rpc_storage, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(!strcmp(request->content.storage_list_request.path, "/")) {
 | 
				
			||||||
 | 
					        rpc_system_storage_list_root(request, context);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Storage* fs_api = furi_record_open("storage");
 | 
					    Storage* fs_api = furi_record_open("storage");
 | 
				
			||||||
    File* dir = storage_file_alloc(fs_api);
 | 
					    File* dir = storage_file_alloc(fs_api);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -132,8 +162,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
 | 
				
			|||||||
            if(i == COUNT_OF(list->file)) {
 | 
					            if(i == COUNT_OF(list->file)) {
 | 
				
			||||||
                list->file_count = i;
 | 
					                list->file_count = i;
 | 
				
			||||||
                response.has_next = true;
 | 
					                response.has_next = true;
 | 
				
			||||||
                rpc_encode_and_send(rpc_storage->rpc, &response);
 | 
					                rpc_send_and_release(rpc_storage->rpc, &response);
 | 
				
			||||||
                pb_release(&PB_Main_msg, &response);
 | 
					 | 
				
			||||||
                i = 0;
 | 
					                i = 0;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ? PB_Storage_File_FileType_DIR :
 | 
					            list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ? PB_Storage_File_FileType_DIR :
 | 
				
			||||||
@ -150,8 +179,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    response.has_next = false;
 | 
					    response.has_next = false;
 | 
				
			||||||
    rpc_encode_and_send(rpc_storage->rpc, &response);
 | 
					    rpc_send_and_release(rpc_storage->rpc, &response);
 | 
				
			||||||
    pb_release(&PB_Main_msg, &response);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    storage_dir_close(dir);
 | 
					    storage_dir_close(dir);
 | 
				
			||||||
    storage_file_free(dir);
 | 
					    storage_file_free(dir);
 | 
				
			||||||
@ -168,9 +196,6 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /* use same message memory to send reponse */
 | 
					    /* use same message memory to send reponse */
 | 
				
			||||||
    PB_Main* response = furi_alloc(sizeof(PB_Main));
 | 
					    PB_Main* response = furi_alloc(sizeof(PB_Main));
 | 
				
			||||||
    response->command_id = request->command_id;
 | 
					 | 
				
			||||||
    response->which_content = PB_Main_storage_read_response_tag;
 | 
					 | 
				
			||||||
    response->command_status = PB_CommandStatus_OK;
 | 
					 | 
				
			||||||
    const char* path = request->content.storage_read_request.path;
 | 
					    const char* path = request->content.storage_read_request.path;
 | 
				
			||||||
    Storage* fs_api = furi_record_open("storage");
 | 
					    Storage* fs_api = furi_record_open("storage");
 | 
				
			||||||
    File* file = storage_file_alloc(fs_api);
 | 
					    File* file = storage_file_alloc(fs_api);
 | 
				
			||||||
@ -178,10 +203,13 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if(storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
 | 
					    if(storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
 | 
				
			||||||
        size_t size_left = storage_file_size(file);
 | 
					        size_t size_left = storage_file_size(file);
 | 
				
			||||||
        response->content.storage_read_response.has_file = true;
 | 
					 | 
				
			||||||
        response->content.storage_read_response.file.data =
 | 
					 | 
				
			||||||
            furi_alloc(PB_BYTES_ARRAY_T_ALLOCSIZE(MIN(size_left, MAX_DATA_SIZE)));
 | 
					 | 
				
			||||||
        do {
 | 
					        do {
 | 
				
			||||||
 | 
					            response->command_id = request->command_id;
 | 
				
			||||||
 | 
					            response->which_content = PB_Main_storage_read_response_tag;
 | 
				
			||||||
 | 
					            response->command_status = PB_CommandStatus_OK;
 | 
				
			||||||
 | 
					            response->content.storage_read_response.has_file = true;
 | 
				
			||||||
 | 
					            response->content.storage_read_response.file.data =
 | 
				
			||||||
 | 
					                furi_alloc(PB_BYTES_ARRAY_T_ALLOCSIZE(MIN(size_left, MAX_DATA_SIZE)));
 | 
				
			||||||
            uint8_t* buffer = response->content.storage_read_response.file.data->bytes;
 | 
					            uint8_t* buffer = response->content.storage_read_response.file.data->bytes;
 | 
				
			||||||
            uint16_t* read_size_msg = &response->content.storage_read_response.file.data->size;
 | 
					            uint16_t* read_size_msg = &response->content.storage_read_response.file.data->size;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -192,21 +220,19 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if(result) {
 | 
					            if(result) {
 | 
				
			||||||
                response->has_next = (size_left > 0);
 | 
					                response->has_next = (size_left > 0);
 | 
				
			||||||
                rpc_encode_and_send(rpc_storage->rpc, response);
 | 
					                rpc_send_and_release(rpc_storage->rpc, response);
 | 
				
			||||||
                // no pb_release(...);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } while((size_left != 0) && result);
 | 
					        } while((size_left != 0) && result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(!result) {
 | 
					        if(!result) {
 | 
				
			||||||
            rpc_encode_and_send_empty(
 | 
					            rpc_send_and_release_empty(
 | 
				
			||||||
                rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file));
 | 
					                rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        rpc_encode_and_send_empty(
 | 
					        rpc_send_and_release_empty(
 | 
				
			||||||
            rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file));
 | 
					            rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pb_release(&PB_Main_msg, response);
 | 
					 | 
				
			||||||
    free(response);
 | 
					    free(response);
 | 
				
			||||||
    storage_file_close(file);
 | 
					    storage_file_close(file);
 | 
				
			||||||
    storage_file_free(file);
 | 
					    storage_file_free(file);
 | 
				
			||||||
@ -245,14 +271,14 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte
 | 
				
			|||||||
        result = (written_size == buffer_size);
 | 
					        result = (written_size == buffer_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(result && !request->has_next) {
 | 
					        if(result && !request->has_next) {
 | 
				
			||||||
            rpc_encode_and_send_empty(
 | 
					            rpc_send_and_release_empty(
 | 
				
			||||||
                rpc_storage->rpc, rpc_storage->current_command_id, PB_CommandStatus_OK);
 | 
					                rpc_storage->rpc, rpc_storage->current_command_id, PB_CommandStatus_OK);
 | 
				
			||||||
            rpc_system_storage_reset_state(rpc_storage, false);
 | 
					            rpc_system_storage_reset_state(rpc_storage, false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(!result) {
 | 
					    if(!result) {
 | 
				
			||||||
        rpc_encode_and_send_empty(
 | 
					        rpc_send_and_release_empty(
 | 
				
			||||||
            rpc_storage->rpc,
 | 
					            rpc_storage->rpc,
 | 
				
			||||||
            rpc_storage->current_command_id,
 | 
					            rpc_storage->current_command_id,
 | 
				
			||||||
            rpc_system_storage_get_file_error(file));
 | 
					            rpc_system_storage_get_file_error(file));
 | 
				
			||||||
@ -260,23 +286,57 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool rpc_system_storage_is_dir_is_empty(Storage* fs_api, const char* path) {
 | 
				
			||||||
 | 
					    FileInfo fileinfo;
 | 
				
			||||||
 | 
					    bool is_dir_is_empty = false;
 | 
				
			||||||
 | 
					    FS_Error error = storage_common_stat(fs_api, path, &fileinfo);
 | 
				
			||||||
 | 
					    if((error == FSE_OK) && (fileinfo.flags & FSF_DIRECTORY)) {
 | 
				
			||||||
 | 
					        File* dir = storage_file_alloc(fs_api);
 | 
				
			||||||
 | 
					        if(storage_dir_open(dir, path)) {
 | 
				
			||||||
 | 
					            char* name = furi_alloc(MAX_NAME_LENGTH);
 | 
				
			||||||
 | 
					            is_dir_is_empty = !storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH);
 | 
				
			||||||
 | 
					            free(name);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        storage_dir_close(dir);
 | 
				
			||||||
 | 
					        storage_file_free(dir);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return is_dir_is_empty;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void rpc_system_storage_delete_process(const PB_Main* request, void* context) {
 | 
					static void rpc_system_storage_delete_process(const PB_Main* request, void* context) {
 | 
				
			||||||
    furi_assert(request);
 | 
					    furi_assert(request);
 | 
				
			||||||
    furi_assert(request->which_content == PB_Main_storage_delete_request_tag);
 | 
					    furi_assert(request->which_content == PB_Main_storage_delete_request_tag);
 | 
				
			||||||
    furi_assert(context);
 | 
					    furi_assert(context);
 | 
				
			||||||
    RpcStorageSystem* rpc_storage = context;
 | 
					    RpcStorageSystem* rpc_storage = context;
 | 
				
			||||||
    PB_CommandStatus status;
 | 
					    PB_CommandStatus status = PB_CommandStatus_ERROR;
 | 
				
			||||||
    rpc_system_storage_reset_state(rpc_storage, true);
 | 
					    rpc_system_storage_reset_state(rpc_storage, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Storage* fs_api = furi_record_open("storage");
 | 
					    Storage* fs_api = furi_record_open("storage");
 | 
				
			||||||
    char* path = request->content.storage_mkdir_request.path;
 | 
					
 | 
				
			||||||
    if(path) {
 | 
					    char* path = request->content.storage_delete_request.path;
 | 
				
			||||||
        FS_Error error = storage_common_remove(fs_api, path);
 | 
					    if(!path) {
 | 
				
			||||||
        status = rpc_system_storage_get_error(error);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        status = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
 | 
					        status = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        FS_Error error_remove = storage_common_remove(fs_api, path);
 | 
				
			||||||
 | 
					        // FSE_DENIED is for empty directory, but not only for this
 | 
				
			||||||
 | 
					        // that's why we have to check it
 | 
				
			||||||
 | 
					        if((error_remove == FSE_DENIED) && !rpc_system_storage_is_dir_is_empty(fs_api, path)) {
 | 
				
			||||||
 | 
					            if(request->content.storage_delete_request.recursive) {
 | 
				
			||||||
 | 
					                bool deleted = storage_simply_remove_recursive(fs_api, path);
 | 
				
			||||||
 | 
					                status = deleted ? PB_CommandStatus_OK : PB_CommandStatus_ERROR;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                status = PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if(error_remove == FSE_NOT_EXIST) {
 | 
				
			||||||
 | 
					            status = PB_CommandStatus_OK;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            status = rpc_system_storage_get_error(error_remove);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    rpc_encode_and_send_empty(rpc_storage->rpc, request->command_id, status);
 | 
					
 | 
				
			||||||
 | 
					    furi_record_close("storage");
 | 
				
			||||||
 | 
					    rpc_send_and_release_empty(rpc_storage->rpc, request->command_id, status);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void rpc_system_storage_mkdir_process(const PB_Main* request, void* context) {
 | 
					static void rpc_system_storage_mkdir_process(const PB_Main* request, void* context) {
 | 
				
			||||||
@ -295,7 +355,7 @@ static void rpc_system_storage_mkdir_process(const PB_Main* request, void* conte
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        status = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
 | 
					        status = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    rpc_encode_and_send_empty(rpc_storage->rpc, request->command_id, status);
 | 
					    rpc_send_and_release_empty(rpc_storage->rpc, request->command_id, status);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void rpc_system_storage_md5sum_process(const PB_Main* request, void* context) {
 | 
					static void rpc_system_storage_md5sum_process(const PB_Main* request, void* context) {
 | 
				
			||||||
@ -307,7 +367,7 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const char* filename = request->content.storage_md5sum_request.path;
 | 
					    const char* filename = request->content.storage_md5sum_request.path;
 | 
				
			||||||
    if(!filename) {
 | 
					    if(!filename) {
 | 
				
			||||||
        rpc_encode_and_send_empty(
 | 
					        rpc_send_and_release_empty(
 | 
				
			||||||
            rpc_storage->rpc, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
 | 
					            rpc_storage->rpc, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -349,9 +409,9 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont
 | 
				
			|||||||
        free(hash);
 | 
					        free(hash);
 | 
				
			||||||
        free(data);
 | 
					        free(data);
 | 
				
			||||||
        storage_file_close(file);
 | 
					        storage_file_close(file);
 | 
				
			||||||
        rpc_encode_and_send(rpc_storage->rpc, &response);
 | 
					        rpc_send_and_release(rpc_storage->rpc, &response);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        rpc_encode_and_send_empty(
 | 
					        rpc_send_and_release_empty(
 | 
				
			||||||
            rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file));
 | 
					            rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,11 @@
 | 
				
			|||||||
 | 
					#include <furi/record.h>
 | 
				
			||||||
 | 
					#include <m-string.h>
 | 
				
			||||||
#include "storage.h"
 | 
					#include "storage.h"
 | 
				
			||||||
#include "storage-i.h"
 | 
					#include "storage-i.h"
 | 
				
			||||||
#include "storage-message.h"
 | 
					#include "storage-message.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define MAX_NAME_LENGTH 256
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define S_API_PROLOGUE                                      \
 | 
					#define S_API_PROLOGUE                                      \
 | 
				
			||||||
    osSemaphoreId_t semaphore = osSemaphoreNew(1, 0, NULL); \
 | 
					    osSemaphoreId_t semaphore = osSemaphoreNew(1, 0, NULL); \
 | 
				
			||||||
    furi_check(semaphore != NULL);
 | 
					    furi_check(semaphore != NULL);
 | 
				
			||||||
@ -382,6 +386,67 @@ void storage_file_free(File* file) {
 | 
				
			|||||||
    free(file);
 | 
					    free(file);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool storage_simply_remove_recursive(Storage* storage, const char* path) {
 | 
				
			||||||
 | 
					    furi_assert(storage);
 | 
				
			||||||
 | 
					    furi_assert(path);
 | 
				
			||||||
 | 
					    FileInfo fileinfo;
 | 
				
			||||||
 | 
					    bool result = false;
 | 
				
			||||||
 | 
					    string_t fullname;
 | 
				
			||||||
 | 
					    string_t cur_dir;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(storage_simply_remove(storage, path)) {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    char* name = furi_alloc(MAX_NAME_LENGTH + 1);
 | 
				
			||||||
 | 
					    File* dir = storage_file_alloc(storage);
 | 
				
			||||||
 | 
					    string_init_set_str(cur_dir, path);
 | 
				
			||||||
 | 
					    bool go_deeper = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while(1) {
 | 
				
			||||||
 | 
					        if(!storage_dir_open(dir, string_get_cstr(cur_dir))) {
 | 
				
			||||||
 | 
					            storage_dir_close(dir);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
 | 
				
			||||||
 | 
					            if(fileinfo.flags & FSF_DIRECTORY) {
 | 
				
			||||||
 | 
					                string_cat_printf(cur_dir, "/%s", name);
 | 
				
			||||||
 | 
					                go_deeper = true;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            string_init_printf(fullname, "%s/%s", string_get_cstr(cur_dir), name);
 | 
				
			||||||
 | 
					            FS_Error error = storage_common_remove(storage, string_get_cstr(fullname));
 | 
				
			||||||
 | 
					            furi_assert(error == FSE_OK);
 | 
				
			||||||
 | 
					            string_clear(fullname);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        storage_dir_close(dir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(go_deeper) {
 | 
				
			||||||
 | 
					            go_deeper = false;
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        FS_Error error = storage_common_remove(storage, string_get_cstr(cur_dir));
 | 
				
			||||||
 | 
					        furi_assert(error == FSE_OK);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(string_cmp(cur_dir, path)) {
 | 
				
			||||||
 | 
					            size_t last_char = string_search_rchar(cur_dir, '/');
 | 
				
			||||||
 | 
					            furi_assert(last_char != STRING_FAILURE);
 | 
				
			||||||
 | 
					            string_left(cur_dir, last_char);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            result = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    storage_file_free(dir);
 | 
				
			||||||
 | 
					    string_clear(cur_dir);
 | 
				
			||||||
 | 
					    free(name);
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool storage_simply_remove(Storage* storage, const char* path) {
 | 
					bool storage_simply_remove(Storage* storage, const char* path) {
 | 
				
			||||||
    FS_Error result;
 | 
					    FS_Error result;
 | 
				
			||||||
    result = storage_common_remove(storage, path);
 | 
					    result = storage_common_remove(storage, path);
 | 
				
			||||||
@ -392,4 +457,4 @@ bool storage_simply_mkdir(Storage* storage, const char* path) {
 | 
				
			|||||||
    FS_Error result;
 | 
					    FS_Error result;
 | 
				
			||||||
    result = storage_common_mkdir(storage, path);
 | 
					    result = storage_common_mkdir(storage, path);
 | 
				
			||||||
    return result == FSE_OK || result == FSE_EXIST;
 | 
					    return result == FSE_OK || result == FSE_EXIST;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -240,6 +240,14 @@ FS_Error storage_sd_status(Storage* api);
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
bool storage_simply_remove(Storage* storage, const char* path);
 | 
					bool storage_simply_remove(Storage* storage, const char* path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Removes a file/directory from the repository, the directory can be not empty
 | 
				
			||||||
 | 
					 * @param storage pointer to the api
 | 
				
			||||||
 | 
					 * @param path
 | 
				
			||||||
 | 
					 * @return true on success or if file/dir is not exist
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					bool storage_simply_remove_recursive(Storage* storage, const char* path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Creates a directory
 | 
					 * Creates a directory
 | 
				
			||||||
 * @param storage 
 | 
					 * @param storage 
 | 
				
			||||||
@ -250,4 +258,4 @@ bool storage_simply_mkdir(Storage* storage, const char* path);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#ifdef __cplusplus
 | 
					#ifdef __cplusplus
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
				
			|||||||
@ -163,15 +163,15 @@ static LFSData* storage_int_lfs_data_alloc() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) {
 | 
					static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) {
 | 
				
			||||||
    int err;
 | 
					    int err;
 | 
				
			||||||
    FuriHalBootFlag boot_flags = furi_hal_boot_get_flags();
 | 
					    FuriHalBootloaderFlag bootloader_flags = furi_hal_bootloader_get_flags();
 | 
				
			||||||
    lfs_t* lfs = &lfs_data->lfs;
 | 
					    lfs_t* lfs = &lfs_data->lfs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(boot_flags & FuriHalBootFlagFactoryReset) {
 | 
					    if(bootloader_flags & FuriHalBootloaderFlagFactoryReset) {
 | 
				
			||||||
        // Factory reset
 | 
					        // Factory reset
 | 
				
			||||||
        err = lfs_format(lfs, &lfs_data->config);
 | 
					        err = lfs_format(lfs, &lfs_data->config);
 | 
				
			||||||
        if(err == 0) {
 | 
					        if(err == 0) {
 | 
				
			||||||
            FURI_LOG_I(TAG, "Factory reset: Format successful, trying to mount");
 | 
					            FURI_LOG_I(TAG, "Factory reset: Format successful, trying to mount");
 | 
				
			||||||
            furi_hal_boot_set_flags(boot_flags & ~FuriHalBootFlagFactoryReset);
 | 
					            furi_hal_bootloader_set_flags(bootloader_flags & ~FuriHalBootloaderFlagFactoryReset);
 | 
				
			||||||
            err = lfs_mount(lfs, &lfs_data->config);
 | 
					            err = lfs_mount(lfs, &lfs_data->config);
 | 
				
			||||||
            if(err == 0) {
 | 
					            if(err == 0) {
 | 
				
			||||||
                FURI_LOG_I(TAG, "Factory reset: Mounted");
 | 
					                FURI_LOG_I(TAG, "Factory reset: Mounted");
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										34
									
								
								applications/subghz/helpers/subghz_custom_event.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								applications/subghz/helpers/subghz_custom_event.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    SubghzCustomEventManagerNoSet = 0,
 | 
				
			||||||
 | 
					    SubghzCustomEventManagerSet,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SubghzCustomEventSceneDeleteSuccess = 100,
 | 
				
			||||||
 | 
					    SubghzCustomEventSceneDelete,
 | 
				
			||||||
 | 
					    SubghzCustomEventSceneReceiverInfoTxStart,
 | 
				
			||||||
 | 
					    SubghzCustomEventSceneReceiverInfoTxStop,
 | 
				
			||||||
 | 
					    SubghzCustomEventSceneReceiverInfoSave,
 | 
				
			||||||
 | 
					    SubghzCustomEventSceneSaveName,
 | 
				
			||||||
 | 
					    SubghzCustomEventSceneSaveSuccess,
 | 
				
			||||||
 | 
					    SubghzCustomEventSceneShowError,
 | 
				
			||||||
 | 
					    SubghzCustomEventSceneShowOnlyRX,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SubghzCustomEventSceneNeedSavingNo,
 | 
				
			||||||
 | 
					    SubghzCustomEventSceneNeedSavingYes,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SubghzCustomEventViewReceverOK,
 | 
				
			||||||
 | 
					    SubghzCustomEventViewReceverConfig,
 | 
				
			||||||
 | 
					    SubghzCustomEventViewReceverBack,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SubghzCustomEventViewReadRAWBack,
 | 
				
			||||||
 | 
					    SubghzCustomEventViewReadRAWIDLE,
 | 
				
			||||||
 | 
					    SubghzCustomEventViewReadRAWREC,
 | 
				
			||||||
 | 
					    SubghzCustomEventViewReadRAWConfig,
 | 
				
			||||||
 | 
					    SubghzCustomEventViewReadRAWMore,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SubghzCustomEventViewTransmitterBack,
 | 
				
			||||||
 | 
					    SubghzCustomEventViewTransmitterSendStart,
 | 
				
			||||||
 | 
					    SubghzCustomEventViewTransmitterSendStop,
 | 
				
			||||||
 | 
					    SubghzCustomEventViewTransmitterError,
 | 
				
			||||||
 | 
					} SubghzCustomEvent;
 | 
				
			||||||
@ -16,4 +16,7 @@ ADD_SCENE(subghz, test_static, TestStatic)
 | 
				
			|||||||
ADD_SCENE(subghz, test_carrier, TestCarrier)
 | 
					ADD_SCENE(subghz, test_carrier, TestCarrier)
 | 
				
			||||||
ADD_SCENE(subghz, test_packet, TestPacket)
 | 
					ADD_SCENE(subghz, test_packet, TestPacket)
 | 
				
			||||||
ADD_SCENE(subghz, set_type, SetType)
 | 
					ADD_SCENE(subghz, set_type, SetType)
 | 
				
			||||||
ADD_SCENE(subghz, frequency_analyzer, FrequencyAnalyzer)
 | 
					ADD_SCENE(subghz, frequency_analyzer, FrequencyAnalyzer)
 | 
				
			||||||
 | 
					ADD_SCENE(subghz, read_raw, ReadRAW)
 | 
				
			||||||
 | 
					ADD_SCENE(subghz, read_raw_menu, ReadRAWMenu)
 | 
				
			||||||
 | 
					ADD_SCENE(subghz, need_saving, NeedSaving)
 | 
				
			||||||
@ -1,15 +1,11 @@
 | 
				
			|||||||
#include "../subghz_i.h"
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
typedef enum {
 | 
					 | 
				
			||||||
    SubGhzSceneDeleteInfoCustomEventDelete,
 | 
					 | 
				
			||||||
} SubGhzSceneDeleteInfoCustomEvent;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_delete_callback(GuiButtonType result, InputType type, void* context) {
 | 
					void subghz_scene_delete_callback(GuiButtonType result, InputType type, void* context) {
 | 
				
			||||||
    furi_assert(context);
 | 
					    furi_assert(context);
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    if((result == GuiButtonTypeRight) && (type == InputTypeShort)) {
 | 
					    if((result == GuiButtonTypeRight) && (type == InputTypeShort)) {
 | 
				
			||||||
        view_dispatcher_send_custom_event(
 | 
					        view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneDelete);
 | 
				
			||||||
            subghz->view_dispatcher, SubGhzSceneDeleteInfoCustomEventDelete);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -53,7 +49,7 @@ void subghz_scene_delete_on_enter(void* context) {
 | 
				
			|||||||
bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) {
 | 
					bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        if(event.event == SubGhzSceneDeleteInfoCustomEventDelete) {
 | 
					        if(event.event == SubghzCustomEventSceneDelete) {
 | 
				
			||||||
            memcpy(subghz->file_name_tmp, subghz->file_name, strlen(subghz->file_name));
 | 
					            memcpy(subghz->file_name_tmp, subghz->file_name, strlen(subghz->file_name));
 | 
				
			||||||
            if(subghz_delete_file(subghz)) {
 | 
					            if(subghz_delete_file(subghz)) {
 | 
				
			||||||
                scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess);
 | 
					                scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,10 @@
 | 
				
			|||||||
#include "../subghz_i.h"
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
#define SCENE_DELETE_SUCCESS_CUSTOM_EVENT (0UL)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_delete_success_popup_callback(void* context) {
 | 
					void subghz_scene_delete_success_popup_callback(void* context) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, SCENE_DELETE_SUCCESS_CUSTOM_EVENT);
 | 
					    view_dispatcher_send_custom_event(
 | 
				
			||||||
 | 
					        subghz->view_dispatcher, SubghzCustomEventSceneDeleteSuccess);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_delete_success_on_enter(void* context) {
 | 
					void subghz_scene_delete_success_on_enter(void* context) {
 | 
				
			||||||
@ -25,7 +25,7 @@ bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event
 | 
				
			|||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        if(event.event == SCENE_DELETE_SUCCESS_CUSTOM_EVENT) {
 | 
					        if(event.event == SubghzCustomEventSceneDeleteSuccess) {
 | 
				
			||||||
            return scene_manager_search_and_switch_to_previous_scene(
 | 
					            return scene_manager_search_and_switch_to_previous_scene(
 | 
				
			||||||
                subghz->scene_manager, SubGhzSceneStart);
 | 
					                subghz->scene_manager, SubGhzSceneStart);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
#include "../subghz_i.h"
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
#include "../views/subghz_frequency_analyzer.h"
 | 
					#include "../views/subghz_frequency_analyzer.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_frequency_analyzer_callback(SubghzFrequencyAnalyzerEvent event, void* context) {
 | 
					void subghz_scene_frequency_analyzer_callback(SubghzCustomEvent event, void* context) {
 | 
				
			||||||
    furi_assert(context);
 | 
					    furi_assert(context);
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 | 
					    view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 | 
				
			||||||
@ -15,13 +15,7 @@ void subghz_scene_frequency_analyzer_on_enter(void* context) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent event) {
 | 
					bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    //SubGhz* subghz = context;
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					 | 
				
			||||||
        if(event.event == SubghzFrequencyAnalyzerEventOnlyRx) {
 | 
					 | 
				
			||||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										63
									
								
								applications/subghz/scenes/subghz_scene_need_saving.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								applications/subghz/scenes/subghz_scene_need_saving.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_scene_need_saving_callback(GuiButtonType result, InputType type, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if((result == GuiButtonTypeRight) && (type == InputTypeShort)) {
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(
 | 
				
			||||||
 | 
					            subghz->view_dispatcher, SubghzCustomEventSceneNeedSavingYes);
 | 
				
			||||||
 | 
					    } else if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) {
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(
 | 
				
			||||||
 | 
					            subghz->view_dispatcher, SubghzCustomEventSceneNeedSavingNo);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_scene_need_saving_on_enter(void* context) {
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_string_multiline_element(
 | 
				
			||||||
 | 
					        subghz->widget,
 | 
				
			||||||
 | 
					        64,
 | 
				
			||||||
 | 
					        25,
 | 
				
			||||||
 | 
					        AlignCenter,
 | 
				
			||||||
 | 
					        AlignCenter,
 | 
				
			||||||
 | 
					        FontSecondary,
 | 
				
			||||||
 | 
					        "There is an unsaved data.\nDo you want to save it?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_button_element(
 | 
				
			||||||
 | 
					        subghz->widget, GuiButtonTypeRight, "Save", subghz_scene_need_saving_callback, subghz);
 | 
				
			||||||
 | 
					    widget_add_button_element(
 | 
				
			||||||
 | 
					        subghz->widget, GuiButtonTypeLeft, "Delete", subghz_scene_need_saving_callback, subghz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewWidget);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        if(event.event == SubghzCustomEventSceneNeedSavingYes) {
 | 
				
			||||||
 | 
					            subghz->txrx->rx_key_state = SubGhzRxKeyStateNeedSave;
 | 
				
			||||||
 | 
					            scene_manager_previous_scene(subghz->scene_manager);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        } else if(event.event == SubghzCustomEventSceneNeedSavingNo) {
 | 
				
			||||||
 | 
					            if(subghz->txrx->rx_key_state == SubGhzRxKeyStateExit) {
 | 
				
			||||||
 | 
					                subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
 | 
				
			||||||
 | 
					                scene_manager_search_and_switch_to_previous_scene(
 | 
				
			||||||
 | 
					                    subghz->scene_manager, SubGhzSceneStart);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
 | 
				
			||||||
 | 
					                scene_manager_previous_scene(subghz->scene_manager);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_scene_need_saving_on_exit(void* context) {
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					    widget_clear(subghz->widget);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										190
									
								
								applications/subghz/scenes/subghz_scene_read_raw.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								applications/subghz/scenes/subghz_scene_read_raw.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,190 @@
 | 
				
			|||||||
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
 | 
					#include "../views/subghz_read_raw.h"
 | 
				
			||||||
 | 
					#include <lib/subghz/protocols/subghz_protocol_raw.h>
 | 
				
			||||||
 | 
					#include <lib/subghz/subghz_parser.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void subghz_scene_read_raw_update_statusbar(void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					    char frequency_str[20];
 | 
				
			||||||
 | 
					    char preset_str[10];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    snprintf(
 | 
				
			||||||
 | 
					        frequency_str,
 | 
				
			||||||
 | 
					        sizeof(frequency_str),
 | 
				
			||||||
 | 
					        "%03ld.%02ld",
 | 
				
			||||||
 | 
					        subghz->txrx->frequency / 1000000 % 1000,
 | 
				
			||||||
 | 
					        subghz->txrx->frequency / 10000 % 100);
 | 
				
			||||||
 | 
					    if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async ||
 | 
				
			||||||
 | 
					       subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) {
 | 
				
			||||||
 | 
					        snprintf(preset_str, sizeof(preset_str), "AM");
 | 
				
			||||||
 | 
					    } else if(
 | 
				
			||||||
 | 
					        subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev238Async ||
 | 
				
			||||||
 | 
					        subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev476Async) {
 | 
				
			||||||
 | 
					        snprintf(preset_str, sizeof(preset_str), "FM");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_crash(NULL);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    subghz_read_raw_add_data_statusbar(subghz->subghz_read_raw, frequency_str, preset_str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_scene_read_raw_callback(SubghzCustomEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					    view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_scene_read_raw_on_enter(void* context) {
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(subghz->txrx->rx_key_state == SubGhzRxKeyStateNeedSave) {
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(
 | 
				
			||||||
 | 
					            subghz->view_dispatcher, SubghzCustomEventViewReadRAWMore);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    subghz_scene_read_raw_update_statusbar(subghz);
 | 
				
			||||||
 | 
					    subghz_read_raw_set_callback(subghz->subghz_read_raw, subghz_scene_read_raw_callback, subghz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    subghz->txrx->protocol_result = subghz_parser_get_by_name(subghz->txrx->parser, "RAW");
 | 
				
			||||||
 | 
					    furi_assert(subghz->txrx->protocol_result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    subghz_worker_set_pair_callback(
 | 
				
			||||||
 | 
					        subghz->txrx->worker, (SubGhzWorkerPairCallback)subghz_parser_raw_parse);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewReadRAW);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        switch(event.event) {
 | 
				
			||||||
 | 
					        case SubghzCustomEventViewReadRAWBack:
 | 
				
			||||||
 | 
					            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
				
			||||||
 | 
					                subghz_rx_end(subghz);
 | 
				
			||||||
 | 
					                subghz_sleep(subghz);
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92];
 | 
				
			||||||
 | 
					            subghz->txrx->preset = FuriHalSubGhzPresetOok650Async;
 | 
				
			||||||
 | 
					            subghz_protocol_raw_save_to_file_stop(
 | 
				
			||||||
 | 
					                (SubGhzProtocolRAW*)subghz->txrx->protocol_result);
 | 
				
			||||||
 | 
					            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if(subghz->txrx->rx_key_state == SubGhzRxKeyStateAddKey) {
 | 
				
			||||||
 | 
					                subghz->txrx->rx_key_state = SubGhzRxKeyStateExit;
 | 
				
			||||||
 | 
					                scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                scene_manager_search_and_switch_to_previous_scene(
 | 
				
			||||||
 | 
					                    subghz->scene_manager, SubGhzSceneStart);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case SubghzCustomEventViewReadRAWConfig:
 | 
				
			||||||
 | 
					            scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                subghz->scene_manager, SubGhzSceneReadRAW, SubghzCustomEventManagerSet);
 | 
				
			||||||
 | 
					            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case SubghzCustomEventViewReadRAWIDLE:
 | 
				
			||||||
 | 
					            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
				
			||||||
 | 
					                subghz_rx_end(subghz);
 | 
				
			||||||
 | 
					                subghz_sleep(subghz);
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            subghz_protocol_raw_save_to_file_stop(
 | 
				
			||||||
 | 
					                (SubGhzProtocolRAW*)subghz->txrx->protocol_result);
 | 
				
			||||||
 | 
					            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case SubghzCustomEventViewReadRAWREC:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if(subghz->txrx->rx_key_state != SubGhzRxKeyStateIDLE) {
 | 
				
			||||||
 | 
					                scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if(subghz_protocol_raw_save_to_file_init(
 | 
				
			||||||
 | 
					                       (SubGhzProtocolRAW*)subghz->txrx->protocol_result,
 | 
				
			||||||
 | 
					                       "Raw_temp",
 | 
				
			||||||
 | 
					                       subghz->txrx->frequency,
 | 
				
			||||||
 | 
					                       subghz->txrx->preset)) {
 | 
				
			||||||
 | 
					                    if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) ||
 | 
				
			||||||
 | 
					                       (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
 | 
				
			||||||
 | 
					                        subghz_begin(subghz, subghz->txrx->preset);
 | 
				
			||||||
 | 
					                        subghz_rx(subghz, subghz->txrx->frequency);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    subghz->state_notifications = NOTIFICATION_RX_STATE;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    string_set(subghz->error_str, "No SD card");
 | 
				
			||||||
 | 
					                    scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case SubghzCustomEventViewReadRAWMore:
 | 
				
			||||||
 | 
					            if(strcmp(
 | 
				
			||||||
 | 
					                   subghz_protocol_get_last_file_name(
 | 
				
			||||||
 | 
					                       (SubGhzProtocolRAW*)subghz->txrx->protocol_result),
 | 
				
			||||||
 | 
					                   "")) {
 | 
				
			||||||
 | 
					                strlcpy(
 | 
				
			||||||
 | 
					                    subghz->file_name,
 | 
				
			||||||
 | 
					                    subghz_protocol_get_last_file_name(
 | 
				
			||||||
 | 
					                        (SubGhzProtocolRAW*)subghz->txrx->protocol_result),
 | 
				
			||||||
 | 
					                    strlen(subghz_protocol_get_last_file_name(
 | 
				
			||||||
 | 
					                        (SubGhzProtocolRAW*)subghz->txrx->protocol_result)) +
 | 
				
			||||||
 | 
					                        1);
 | 
				
			||||||
 | 
					                //set the path to read the file
 | 
				
			||||||
 | 
					                string_t temp_str;
 | 
				
			||||||
 | 
					                string_init_printf(
 | 
				
			||||||
 | 
					                    temp_str,
 | 
				
			||||||
 | 
					                    "%s/%s%s",
 | 
				
			||||||
 | 
					                    SUBGHZ_APP_PATH_FOLDER,
 | 
				
			||||||
 | 
					                    subghz->file_name,
 | 
				
			||||||
 | 
					                    SUBGHZ_APP_EXTENSION);
 | 
				
			||||||
 | 
					                subghz_protocol_set_last_file_name(
 | 
				
			||||||
 | 
					                    (SubGhzProtocolRAW*)subghz->txrx->protocol_result, string_get_cstr(temp_str));
 | 
				
			||||||
 | 
					                string_clear(temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAWMenu);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if(event.type == SceneManagerEventTypeTick) {
 | 
				
			||||||
 | 
					        switch(subghz->state_notifications) {
 | 
				
			||||||
 | 
					        case NOTIFICATION_RX_STATE:
 | 
				
			||||||
 | 
					            notification_message(subghz->notifications, &sequence_blink_blue_10);
 | 
				
			||||||
 | 
					            subghz_read_raw_update_sample_write(
 | 
				
			||||||
 | 
					                subghz->subghz_read_raw,
 | 
				
			||||||
 | 
					                subghz_protocol_raw_get_sample_write(
 | 
				
			||||||
 | 
					                    (SubGhzProtocolRAW*)subghz->txrx->protocol_result));
 | 
				
			||||||
 | 
					            subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, furi_hal_subghz_get_rssi());
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_scene_read_raw_on_exit(void* context) {
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //Stop CC1101
 | 
				
			||||||
 | 
					    if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
				
			||||||
 | 
					        subghz_rx_end(subghz);
 | 
				
			||||||
 | 
					        subghz_sleep(subghz);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //Сallback restoration
 | 
				
			||||||
 | 
					    subghz_worker_set_pair_callback(
 | 
				
			||||||
 | 
					        subghz->txrx->worker, (SubGhzWorkerPairCallback)subghz_parser_parse);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										72
									
								
								applications/subghz/scenes/subghz_scene_read_raw_menu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								applications/subghz/scenes/subghz_scene_read_raw_menu.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum SubmenuIndex {
 | 
				
			||||||
 | 
					    SubmenuIndexEmulate,
 | 
				
			||||||
 | 
					    SubmenuIndexEdit,
 | 
				
			||||||
 | 
					    SubmenuIndexDelete,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_scene_read_raw_menu_submenu_callback(void* context, uint32_t index) {
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					    view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_scene_read_raw_menu_on_enter(void* context) {
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					    submenu_add_item(
 | 
				
			||||||
 | 
					        subghz->submenu,
 | 
				
			||||||
 | 
					        "Emulate",
 | 
				
			||||||
 | 
					        SubmenuIndexEmulate,
 | 
				
			||||||
 | 
					        subghz_scene_read_raw_menu_submenu_callback,
 | 
				
			||||||
 | 
					        subghz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    submenu_add_item(
 | 
				
			||||||
 | 
					        subghz->submenu,
 | 
				
			||||||
 | 
					        "Save",
 | 
				
			||||||
 | 
					        SubmenuIndexEdit,
 | 
				
			||||||
 | 
					        subghz_scene_read_raw_menu_submenu_callback,
 | 
				
			||||||
 | 
					        subghz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    submenu_add_item(
 | 
				
			||||||
 | 
					        subghz->submenu,
 | 
				
			||||||
 | 
					        "Delete",
 | 
				
			||||||
 | 
					        SubmenuIndexDelete,
 | 
				
			||||||
 | 
					        subghz_scene_read_raw_menu_submenu_callback,
 | 
				
			||||||
 | 
					        subghz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    submenu_set_selected_item(
 | 
				
			||||||
 | 
					        subghz->submenu,
 | 
				
			||||||
 | 
					        scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSavedMenu));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewMenu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool subghz_scene_read_raw_menu_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        if(event.event == SubmenuIndexEmulate) {
 | 
				
			||||||
 | 
					            scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                subghz->scene_manager, SubGhzSceneReadRAWMenu, SubmenuIndexEmulate);
 | 
				
			||||||
 | 
					            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        } else if(event.event == SubmenuIndexDelete) {
 | 
				
			||||||
 | 
					            scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                subghz->scene_manager, SubGhzSceneReadRAWMenu, SubmenuIndexDelete);
 | 
				
			||||||
 | 
					            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDelete);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        } else if(event.event == SubmenuIndexEdit) {
 | 
				
			||||||
 | 
					            scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                subghz->scene_manager, SubGhzSceneReadRAWMenu, SubghzCustomEventManagerSet);
 | 
				
			||||||
 | 
					            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_scene_read_raw_menu_on_exit(void* context) {
 | 
				
			||||||
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					    submenu_clean(subghz->submenu);
 | 
				
			||||||
 | 
					    subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -34,7 +34,7 @@ static void subghz_scene_receiver_update_statusbar(void* context) {
 | 
				
			|||||||
    string_clear(history_stat_str);
 | 
					    string_clear(history_stat_str);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_receiver_callback(SubghzReceverEvent event, void* context) {
 | 
					void subghz_scene_receiver_callback(SubghzCustomEvent event, void* context) {
 | 
				
			||||||
    furi_assert(context);
 | 
					    furi_assert(context);
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 | 
					    view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 | 
				
			||||||
@ -87,7 +87,7 @@ void subghz_scene_receiver_on_enter(void* context) {
 | 
				
			|||||||
    if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
					    if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
				
			||||||
        subghz_rx_end(subghz);
 | 
					        subghz_rx_end(subghz);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    if((subghz->txrx->txrx_state == SubGhzTxRxStateIdle) ||
 | 
					    if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) ||
 | 
				
			||||||
       (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
 | 
					       (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
 | 
				
			||||||
        subghz_begin(subghz, subghz->txrx->preset);
 | 
					        subghz_begin(subghz, subghz->txrx->preset);
 | 
				
			||||||
        subghz_rx(subghz, subghz->txrx->frequency);
 | 
					        subghz_rx(subghz, subghz->txrx->frequency);
 | 
				
			||||||
@ -102,8 +102,9 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        switch(event.event) {
 | 
					        switch(event.event) {
 | 
				
			||||||
        case SubghzReceverEventBack:
 | 
					        case SubghzCustomEventViewReceverBack:
 | 
				
			||||||
            // Stop CC1101 Rx
 | 
					            // Stop CC1101 Rx
 | 
				
			||||||
 | 
					            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
				
			||||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
					            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
				
			||||||
                subghz_rx_end(subghz);
 | 
					                subghz_rx_end(subghz);
 | 
				
			||||||
                subghz_sleep(subghz);
 | 
					                subghz_sleep(subghz);
 | 
				
			||||||
@ -118,12 +119,12 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
                subghz->scene_manager, SubGhzSceneStart);
 | 
					                subghz->scene_manager, SubGhzSceneStart);
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        case SubghzReceverEventOK:
 | 
					        case SubghzCustomEventViewReceverOK:
 | 
				
			||||||
            subghz->txrx->idx_menu_chosen = subghz_receiver_get_idx_menu(subghz->subghz_receiver);
 | 
					            subghz->txrx->idx_menu_chosen = subghz_receiver_get_idx_menu(subghz->subghz_receiver);
 | 
				
			||||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo);
 | 
					            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo);
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        case SubghzReceverEventConfig:
 | 
					        case SubghzCustomEventViewReceverConfig:
 | 
				
			||||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
					            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
				
			||||||
            subghz->txrx->idx_menu_chosen = subghz_receiver_get_idx_menu(subghz->subghz_receiver);
 | 
					            subghz->txrx->idx_menu_chosen = subghz_receiver_get_idx_menu(subghz->subghz_receiver);
 | 
				
			||||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig);
 | 
					            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig);
 | 
				
			||||||
 | 
				
			|||||||
@ -66,6 +66,8 @@ static void subghz_scene_receiver_config_set_frequency(VariableItem* item) {
 | 
				
			|||||||
    if(subghz->txrx->hopper_state == SubGhzHopperStateOFF) {
 | 
					    if(subghz->txrx->hopper_state == SubGhzHopperStateOFF) {
 | 
				
			||||||
        variable_item_set_current_value_text(item, subghz_frequencies_text[index]);
 | 
					        variable_item_set_current_value_text(item, subghz_frequencies_text[index]);
 | 
				
			||||||
        subghz->txrx->frequency = subghz_frequencies[index];
 | 
					        subghz->txrx->frequency = subghz_frequencies[index];
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        variable_item_set_current_value_index(item, subghz_frequencies_433_92);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -88,22 +90,24 @@ static void subghz_scene_receiver_config_set_hopping_runing(VariableItem* item)
 | 
				
			|||||||
                subghz->scene_manager, SubGhzSceneReceiverConfig),
 | 
					                subghz->scene_manager, SubGhzSceneReceiverConfig),
 | 
				
			||||||
            subghz_frequencies_text[subghz_frequencies_433_92]);
 | 
					            subghz_frequencies_text[subghz_frequencies_433_92]);
 | 
				
			||||||
        subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92];
 | 
					        subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92];
 | 
				
			||||||
 | 
					        variable_item_set_current_value_index(
 | 
				
			||||||
 | 
					            (VariableItem*)scene_manager_get_scene_state(
 | 
				
			||||||
 | 
					                subghz->scene_manager, SubGhzSceneReceiverConfig),
 | 
				
			||||||
 | 
					            subghz_frequencies_433_92);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        variable_item_set_current_value_text(
 | 
					        variable_item_set_current_value_text(
 | 
				
			||||||
            (VariableItem*)scene_manager_get_scene_state(
 | 
					            (VariableItem*)scene_manager_get_scene_state(
 | 
				
			||||||
                subghz->scene_manager, SubGhzSceneReceiverConfig),
 | 
					                subghz->scene_manager, SubGhzSceneReceiverConfig),
 | 
				
			||||||
            " -----");
 | 
					            " -----");
 | 
				
			||||||
 | 
					        variable_item_set_current_value_index(
 | 
				
			||||||
 | 
					            (VariableItem*)scene_manager_get_scene_state(
 | 
				
			||||||
 | 
					                subghz->scene_manager, SubGhzSceneReceiverConfig),
 | 
				
			||||||
 | 
					            subghz_frequencies_433_92);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    subghz->txrx->hopper_state = hopping_value[index];
 | 
					    subghz->txrx->hopper_state = hopping_value[index];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_receiver_config_callback(SubghzReceverEvent event, void* context) {
 | 
					 | 
				
			||||||
    furi_assert(context);
 | 
					 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					 | 
				
			||||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void subghz_scene_receiver_config_on_enter(void* context) {
 | 
					void subghz_scene_receiver_config_on_enter(void* context) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    VariableItem* item;
 | 
					    VariableItem* item;
 | 
				
			||||||
@ -122,16 +126,19 @@ void subghz_scene_receiver_config_on_enter(void* context) {
 | 
				
			|||||||
    variable_item_set_current_value_index(item, value_index);
 | 
					    variable_item_set_current_value_index(item, value_index);
 | 
				
			||||||
    variable_item_set_current_value_text(item, subghz_frequencies_text[value_index]);
 | 
					    variable_item_set_current_value_text(item, subghz_frequencies_text[value_index]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    item = variable_item_list_add(
 | 
					    if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
 | 
				
			||||||
        subghz->variable_item_list,
 | 
					       SubghzCustomEventManagerSet) {
 | 
				
			||||||
        "Hopping:",
 | 
					        item = variable_item_list_add(
 | 
				
			||||||
        HOPPING_COUNT,
 | 
					            subghz->variable_item_list,
 | 
				
			||||||
        subghz_scene_receiver_config_set_hopping_runing,
 | 
					            "Hopping:",
 | 
				
			||||||
        subghz);
 | 
					            HOPPING_COUNT,
 | 
				
			||||||
    value_index = subghz_scene_receiver_config_hopper_value_index(
 | 
					            subghz_scene_receiver_config_set_hopping_runing,
 | 
				
			||||||
        subghz->txrx->hopper_state, hopping_value, HOPPING_COUNT, subghz);
 | 
					            subghz);
 | 
				
			||||||
    variable_item_set_current_value_index(item, value_index);
 | 
					        value_index = subghz_scene_receiver_config_hopper_value_index(
 | 
				
			||||||
    variable_item_set_current_value_text(item, hopping_text[value_index]);
 | 
					            subghz->txrx->hopper_state, hopping_value, HOPPING_COUNT, subghz);
 | 
				
			||||||
 | 
					        variable_item_set_current_value_index(item, value_index);
 | 
				
			||||||
 | 
					        variable_item_set_current_value_text(item, hopping_text[value_index]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    item = variable_item_list_add(
 | 
					    item = variable_item_list_add(
 | 
				
			||||||
        subghz->variable_item_list,
 | 
					        subghz->variable_item_list,
 | 
				
			||||||
@ -155,4 +162,6 @@ bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent even
 | 
				
			|||||||
void subghz_scene_receiver_config_on_exit(void* context) {
 | 
					void subghz_scene_receiver_config_on_exit(void* context) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    variable_item_list_clean(subghz->variable_item_list);
 | 
					    variable_item_list_clean(subghz->variable_item_list);
 | 
				
			||||||
 | 
					    scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					        subghz->scene_manager, SubGhzSceneReadRAW, SubghzCustomEventManagerNoSet);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,5 @@
 | 
				
			|||||||
#include "../subghz_i.h"
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
typedef enum {
 | 
					 | 
				
			||||||
    SubGhzSceneReceiverInfoCustomEventTxStart,
 | 
					 | 
				
			||||||
    SubGhzSceneReceiverInfoCustomEventTxStop,
 | 
					 | 
				
			||||||
    SubGhzSceneReceiverInfoCustomEventSave,
 | 
					 | 
				
			||||||
} SubGhzSceneReceiverInfoCustomEvent;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) {
 | 
					void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) {
 | 
				
			||||||
    furi_assert(context);
 | 
					    furi_assert(context);
 | 
				
			||||||
@ -12,13 +7,13 @@ void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, v
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if((result == GuiButtonTypeCenter) && (type == InputTypePress)) {
 | 
					    if((result == GuiButtonTypeCenter) && (type == InputTypePress)) {
 | 
				
			||||||
        view_dispatcher_send_custom_event(
 | 
					        view_dispatcher_send_custom_event(
 | 
				
			||||||
            subghz->view_dispatcher, SubGhzSceneReceiverInfoCustomEventTxStart);
 | 
					            subghz->view_dispatcher, SubghzCustomEventSceneReceiverInfoTxStart);
 | 
				
			||||||
    } else if((result == GuiButtonTypeCenter) && (type == InputTypeRelease)) {
 | 
					    } else if((result == GuiButtonTypeCenter) && (type == InputTypeRelease)) {
 | 
				
			||||||
        view_dispatcher_send_custom_event(
 | 
					        view_dispatcher_send_custom_event(
 | 
				
			||||||
            subghz->view_dispatcher, SubGhzSceneReceiverInfoCustomEventTxStop);
 | 
					            subghz->view_dispatcher, SubghzCustomEventSceneReceiverInfoTxStop);
 | 
				
			||||||
    } else if((result == GuiButtonTypeRight) && (type == InputTypeShort)) {
 | 
					    } else if((result == GuiButtonTypeRight) && (type == InputTypeShort)) {
 | 
				
			||||||
        view_dispatcher_send_custom_event(
 | 
					        view_dispatcher_send_custom_event(
 | 
				
			||||||
            subghz->view_dispatcher, SubGhzSceneReceiverInfoCustomEventSave);
 | 
					            subghz->view_dispatcher, SubghzCustomEventSceneReceiverInfoSave);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -101,7 +96,7 @@ void subghz_scene_receiver_info_on_enter(void* context) {
 | 
				
			|||||||
bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) {
 | 
					bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        if(event.event == SubGhzSceneReceiverInfoCustomEventTxStart) {
 | 
					        if(event.event == SubghzCustomEventSceneReceiverInfoTxStart) {
 | 
				
			||||||
            //CC1101 Stop RX -> Start TX
 | 
					            //CC1101 Stop RX -> Start TX
 | 
				
			||||||
            if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
 | 
					            if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
 | 
				
			||||||
                subghz->txrx->hopper_state = SubGhzHopperStatePause;
 | 
					                subghz->txrx->hopper_state = SubGhzHopperStatePause;
 | 
				
			||||||
@ -112,7 +107,8 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
 | 
				
			|||||||
            if(!subghz_scene_receiver_info_update_parser(subghz)) {
 | 
					            if(!subghz_scene_receiver_info_update_parser(subghz)) {
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) {
 | 
					            if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE ||
 | 
				
			||||||
 | 
					               subghz->txrx->txrx_state == SubGhzTxRxStateSleep) {
 | 
				
			||||||
                if(!subghz_tx_start(subghz)) {
 | 
					                if(!subghz_tx_start(subghz)) {
 | 
				
			||||||
                    scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
 | 
					                    scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
@ -120,13 +116,13 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        } else if(event.event == SubGhzSceneReceiverInfoCustomEventTxStop) {
 | 
					        } else if(event.event == SubghzCustomEventSceneReceiverInfoTxStop) {
 | 
				
			||||||
            //CC1101 Stop Tx -> Start RX
 | 
					            //CC1101 Stop Tx -> Start RX
 | 
				
			||||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
					            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
				
			||||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
 | 
					            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
 | 
				
			||||||
                subghz_tx_stop(subghz);
 | 
					                subghz_tx_stop(subghz);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) {
 | 
					            if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) {
 | 
				
			||||||
                subghz_begin(subghz, subghz->txrx->preset);
 | 
					                subghz_begin(subghz, subghz->txrx->preset);
 | 
				
			||||||
                subghz_rx(subghz, subghz->txrx->frequency);
 | 
					                subghz_rx(subghz, subghz->txrx->frequency);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -135,7 +131,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            subghz->state_notifications = NOTIFICATION_RX_STATE;
 | 
					            subghz->state_notifications = NOTIFICATION_RX_STATE;
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        } else if(event.event == SubGhzSceneReceiverInfoCustomEventSave) {
 | 
					        } else if(event.event == SubghzCustomEventSceneReceiverInfoSave) {
 | 
				
			||||||
            //CC1101 Stop RX -> Save
 | 
					            //CC1101 Stop RX -> Save
 | 
				
			||||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
					            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
				
			||||||
            if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
 | 
					            if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,11 @@
 | 
				
			|||||||
#include "../subghz_i.h"
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
#include <lib/toolbox/random_name.h>
 | 
					#include <lib/toolbox/random_name.h>
 | 
				
			||||||
#include "file-worker.h"
 | 
					#include "file-worker.h"
 | 
				
			||||||
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
#define SCENE_SAVE_NAME_CUSTOM_EVENT (0UL)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_save_name_text_input_callback(void* context) {
 | 
					void subghz_scene_save_name_text_input_callback(void* context) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, SCENE_SAVE_NAME_CUSTOM_EVENT);
 | 
					    view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneSaveName);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_save_name_on_enter(void* context) {
 | 
					void subghz_scene_save_name_on_enter(void* context) {
 | 
				
			||||||
@ -21,6 +20,10 @@ void subghz_scene_save_name_on_enter(void* context) {
 | 
				
			|||||||
        dev_name_empty = true;
 | 
					        dev_name_empty = true;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        memcpy(subghz->file_name_tmp, subghz->file_name, strlen(subghz->file_name));
 | 
					        memcpy(subghz->file_name_tmp, subghz->file_name, strlen(subghz->file_name));
 | 
				
			||||||
 | 
					        if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAWMenu) ==
 | 
				
			||||||
 | 
					           SubghzCustomEventManagerSet) {
 | 
				
			||||||
 | 
					            subghz_get_next_name_file(subghz);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    text_input_set_header_text(text_input, "Name signal");
 | 
					    text_input_set_header_text(text_input, "Name signal");
 | 
				
			||||||
@ -38,12 +41,14 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        if(event.event == SCENE_SAVE_NAME_CUSTOM_EVENT) {
 | 
					        if(event.event == SubghzCustomEventSceneSaveName) {
 | 
				
			||||||
            if(strcmp(subghz->file_name, "") &&
 | 
					            if(strcmp(subghz->file_name, "")) {
 | 
				
			||||||
               subghz_save_protocol_to_file(subghz, subghz->file_name)) {
 | 
					 | 
				
			||||||
                if(strcmp(subghz->file_name_tmp, "")) {
 | 
					                if(strcmp(subghz->file_name_tmp, "")) {
 | 
				
			||||||
                    subghz_delete_file(subghz);
 | 
					                    subghz_rename_file(subghz);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    subghz_save_protocol_to_file(subghz, subghz->file_name);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                subghz_file_name_clear(subghz);
 | 
					                subghz_file_name_clear(subghz);
 | 
				
			||||||
                scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess);
 | 
					                scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess);
 | 
				
			||||||
                return true;
 | 
					                return true;
 | 
				
			||||||
@ -62,4 +67,6 @@ void subghz_scene_save_name_on_exit(void* context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Clear view
 | 
					    // Clear view
 | 
				
			||||||
    text_input_clean(subghz->text_input);
 | 
					    text_input_clean(subghz->text_input);
 | 
				
			||||||
 | 
					    scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					        subghz->scene_manager, SubGhzSceneReadRAWMenu, SubghzCustomEventManagerNoSet);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,9 @@
 | 
				
			|||||||
#include "../subghz_i.h"
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
#define SCENE_SAVE_SUCCESS_CUSTOM_EVENT (0UL)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_save_success_popup_callback(void* context) {
 | 
					void subghz_scene_save_success_popup_callback(void* context) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, SCENE_SAVE_SUCCESS_CUSTOM_EVENT);
 | 
					    view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneSaveSuccess);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_save_success_on_enter(void* context) {
 | 
					void subghz_scene_save_success_on_enter(void* context) {
 | 
				
			||||||
@ -24,7 +23,7 @@ void subghz_scene_save_success_on_enter(void* context) {
 | 
				
			|||||||
bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event) {
 | 
					bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        if(event.event == SCENE_SAVE_SUCCESS_CUSTOM_EVENT) {
 | 
					        if(event.event == SubghzCustomEventSceneSaveSuccess) {
 | 
				
			||||||
            if(!scene_manager_search_and_switch_to_previous_scene(
 | 
					            if(!scene_manager_search_and_switch_to_previous_scene(
 | 
				
			||||||
                   subghz->scene_manager, SubGhzSceneReceiver)) {
 | 
					                   subghz->scene_manager, SubGhzSceneReceiver)) {
 | 
				
			||||||
                scene_manager_search_and_switch_to_previous_scene(
 | 
					                scene_manager_search_and_switch_to_previous_scene(
 | 
				
			||||||
 | 
				
			|||||||
@ -19,12 +19,14 @@ void subghz_scene_saved_menu_on_enter(void* context) {
 | 
				
			|||||||
        SubmenuIndexEmulate,
 | 
					        SubmenuIndexEmulate,
 | 
				
			||||||
        subghz_scene_saved_menu_submenu_callback,
 | 
					        subghz_scene_saved_menu_submenu_callback,
 | 
				
			||||||
        subghz);
 | 
					        subghz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    submenu_add_item(
 | 
					    submenu_add_item(
 | 
				
			||||||
        subghz->submenu,
 | 
					        subghz->submenu,
 | 
				
			||||||
        "Edit name",
 | 
					        "Edit name",
 | 
				
			||||||
        SubmenuIndexEdit,
 | 
					        SubmenuIndexEdit,
 | 
				
			||||||
        subghz_scene_saved_menu_submenu_callback,
 | 
					        subghz_scene_saved_menu_submenu_callback,
 | 
				
			||||||
        subghz);
 | 
					        subghz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    submenu_add_item(
 | 
					    submenu_add_item(
 | 
				
			||||||
        subghz->submenu,
 | 
					        subghz->submenu,
 | 
				
			||||||
        "Delete",
 | 
					        "Delete",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,9 @@
 | 
				
			|||||||
#include "../subghz_i.h"
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
#define SCENE_NO_MAN_CUSTOM_EVENT (11UL)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_show_error_popup_callback(void* context) {
 | 
					void subghz_scene_show_error_popup_callback(void* context) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, SCENE_NO_MAN_CUSTOM_EVENT);
 | 
					    view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneShowError);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_show_error_on_enter(void* context) {
 | 
					void subghz_scene_show_error_on_enter(void* context) {
 | 
				
			||||||
@ -24,7 +23,7 @@ void subghz_scene_show_error_on_enter(void* context) {
 | 
				
			|||||||
bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) {
 | 
					bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        if(event.event == SCENE_NO_MAN_CUSTOM_EVENT) {
 | 
					        if(event.event == SubghzCustomEventSceneShowError) {
 | 
				
			||||||
            scene_manager_search_and_switch_to_previous_scene(
 | 
					            scene_manager_search_and_switch_to_previous_scene(
 | 
				
			||||||
                subghz->scene_manager, SubGhzSceneStart);
 | 
					                subghz->scene_manager, SubGhzSceneStart);
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,9 @@
 | 
				
			|||||||
#include "../subghz_i.h"
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
#define SCENE_NO_MAN_CUSTOM_EVENT (11UL)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_show_only_rx_popup_callback(void* context) {
 | 
					void subghz_scene_show_only_rx_popup_callback(void* context) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, SCENE_NO_MAN_CUSTOM_EVENT);
 | 
					    view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneShowOnlyRX);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_show_only_rx_on_enter(void* context) {
 | 
					void subghz_scene_show_only_rx_on_enter(void* context) {
 | 
				
			||||||
@ -30,7 +29,7 @@ void subghz_scene_show_only_rx_on_enter(void* context) {
 | 
				
			|||||||
const bool subghz_scene_show_only_rx_on_event(void* context, SceneManagerEvent event) {
 | 
					const bool subghz_scene_show_only_rx_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        if(event.event == SCENE_NO_MAN_CUSTOM_EVENT) {
 | 
					        if(event.event == SubghzCustomEventSceneShowOnlyRX) {
 | 
				
			||||||
            scene_manager_previous_scene(subghz->scene_manager);
 | 
					            scene_manager_previous_scene(subghz->scene_manager);
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,12 @@
 | 
				
			|||||||
#include "../subghz_i.h"
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum SubmenuIndex {
 | 
					enum SubmenuIndex {
 | 
				
			||||||
    SubmenuIndexRead,
 | 
					    SubmenuIndexRead = 10,
 | 
				
			||||||
    SubmenuIndexSaved,
 | 
					    SubmenuIndexSaved,
 | 
				
			||||||
    SubmenuIndexTest,
 | 
					    SubmenuIndexTest,
 | 
				
			||||||
    SubmenuIndexAddManualy,
 | 
					    SubmenuIndexAddManualy,
 | 
				
			||||||
    SubmenuIndexFrequencyAnalyzer,
 | 
					    SubmenuIndexFrequencyAnalyzer,
 | 
				
			||||||
 | 
					    SubmenuIndexReadRAW,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_start_submenu_callback(void* context, uint32_t index) {
 | 
					void subghz_scene_start_submenu_callback(void* context, uint32_t index) {
 | 
				
			||||||
@ -20,6 +21,12 @@ void subghz_scene_start_on_enter(void* context) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    submenu_add_item(
 | 
					    submenu_add_item(
 | 
				
			||||||
        subghz->submenu, "Read", SubmenuIndexRead, subghz_scene_start_submenu_callback, subghz);
 | 
					        subghz->submenu, "Read", SubmenuIndexRead, subghz_scene_start_submenu_callback, subghz);
 | 
				
			||||||
 | 
					    submenu_add_item(
 | 
				
			||||||
 | 
					        subghz->submenu,
 | 
				
			||||||
 | 
					        "Read Raw",
 | 
				
			||||||
 | 
					        SubmenuIndexReadRAW,
 | 
				
			||||||
 | 
					        subghz_scene_start_submenu_callback,
 | 
				
			||||||
 | 
					        subghz);
 | 
				
			||||||
    submenu_add_item(
 | 
					    submenu_add_item(
 | 
				
			||||||
        subghz->submenu, "Saved", SubmenuIndexSaved, subghz_scene_start_submenu_callback, subghz);
 | 
					        subghz->submenu, "Saved", SubmenuIndexSaved, subghz_scene_start_submenu_callback, subghz);
 | 
				
			||||||
    submenu_add_item(
 | 
					    submenu_add_item(
 | 
				
			||||||
@ -47,7 +54,12 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        if(event.event == SubmenuIndexRead) {
 | 
					        if(event.event == SubmenuIndexReadRAW) {
 | 
				
			||||||
 | 
					            scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                subghz->scene_manager, SubGhzSceneStart, SubmenuIndexReadRAW);
 | 
				
			||||||
 | 
					            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        } else if(event.event == SubmenuIndexRead) {
 | 
				
			||||||
            scene_manager_set_scene_state(
 | 
					            scene_manager_set_scene_state(
 | 
				
			||||||
                subghz->scene_manager, SubGhzSceneStart, SubmenuIndexRead);
 | 
					                subghz->scene_manager, SubGhzSceneStart, SubmenuIndexRead);
 | 
				
			||||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver);
 | 
					            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver);
 | 
				
			||||||
 | 
				
			|||||||
@ -2,13 +2,13 @@
 | 
				
			|||||||
#include "../views/subghz_transmitter.h"
 | 
					#include "../views/subghz_transmitter.h"
 | 
				
			||||||
#include <lib/subghz/protocols/subghz_protocol_keeloq.h>
 | 
					#include <lib/subghz/protocols/subghz_protocol_keeloq.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_transmitter_callback(SubghzTransmitterEvent event, void* context) {
 | 
					void subghz_scene_transmitter_callback(SubghzCustomEvent event, void* context) {
 | 
				
			||||||
    furi_assert(context);
 | 
					    furi_assert(context);
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 | 
					    view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void subghz_scene_transmitter_update_data_show(void* context) {
 | 
					bool subghz_scene_transmitter_update_data_show(void* context) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->get_upload_protocol) {
 | 
					    if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->get_upload_protocol) {
 | 
				
			||||||
@ -51,17 +51,22 @@ static void subghz_scene_transmitter_update_data_show(void* context) {
 | 
				
			|||||||
            preset_str,
 | 
					            preset_str,
 | 
				
			||||||
            show_button);
 | 
					            show_button);
 | 
				
			||||||
        string_clear(key_str);
 | 
					        string_clear(key_str);
 | 
				
			||||||
    } else {
 | 
					
 | 
				
			||||||
        string_set(subghz->error_str, "Protocol not found");
 | 
					        return true;
 | 
				
			||||||
        scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_scene_transmitter_on_enter(void* context) {
 | 
					void subghz_scene_transmitter_on_enter(void* context) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
 | 
					    if(!subghz_scene_transmitter_update_data_show(subghz)) {
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(
 | 
				
			||||||
 | 
					            subghz->view_dispatcher, SubghzCustomEventViewTransmitterError);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    subghz_transmitter_set_callback(
 | 
					    subghz_transmitter_set_callback(
 | 
				
			||||||
        subghz->subghz_transmitter, subghz_scene_transmitter_callback, subghz);
 | 
					        subghz->subghz_transmitter, subghz_scene_transmitter_callback, subghz);
 | 
				
			||||||
    subghz_scene_transmitter_update_data_show(subghz);
 | 
					
 | 
				
			||||||
    subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
					    subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
				
			||||||
    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewTransmitter);
 | 
					    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewTransmitter);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -69,12 +74,12 @@ void subghz_scene_transmitter_on_enter(void* context) {
 | 
				
			|||||||
bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) {
 | 
					bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    SubGhz* subghz = context;
 | 
					    SubGhz* subghz = context;
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        if(event.event == SubghzTransmitterEventSendStart) {
 | 
					        if(event.event == SubghzCustomEventViewTransmitterSendStart) {
 | 
				
			||||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
					            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
				
			||||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
					            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
				
			||||||
                subghz_rx_end(subghz);
 | 
					                subghz_rx_end(subghz);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if((subghz->txrx->txrx_state == SubGhzTxRxStateIdle) ||
 | 
					            if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) ||
 | 
				
			||||||
               (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
 | 
					               (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
 | 
				
			||||||
                if(!subghz_tx_start(subghz)) {
 | 
					                if(!subghz_tx_start(subghz)) {
 | 
				
			||||||
                    scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
 | 
					                    scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
 | 
				
			||||||
@ -84,18 +89,21 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        } else if(event.event == SubghzTransmitterEventSendStop) {
 | 
					        } else if(event.event == SubghzCustomEventViewTransmitterSendStop) {
 | 
				
			||||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
					            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
				
			||||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
 | 
					            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
 | 
				
			||||||
                subghz_tx_stop(subghz);
 | 
					                subghz_tx_stop(subghz);
 | 
				
			||||||
                subghz_sleep(subghz);
 | 
					                subghz_sleep(subghz);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        } else if(event.event == SubghzTransmitterEventBack) {
 | 
					        } else if(event.event == SubghzCustomEventViewTransmitterBack) {
 | 
				
			||||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
					            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
				
			||||||
            scene_manager_search_and_switch_to_previous_scene(
 | 
					            scene_manager_search_and_switch_to_previous_scene(
 | 
				
			||||||
                subghz->scene_manager, SubGhzSceneStart);
 | 
					                subghz->scene_manager, SubGhzSceneStart);
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
 | 
					        } else if(event.event == SubghzCustomEventViewTransmitterError) {
 | 
				
			||||||
 | 
					            string_set(subghz->error_str, "Protocol not found");
 | 
				
			||||||
 | 
					            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else if(event.type == SceneManagerEventTypeTick) {
 | 
					    } else if(event.type == SceneManagerEventTypeTick) {
 | 
				
			||||||
        if(subghz->state_notifications == NOTIFICATION_TX_STATE) {
 | 
					        if(subghz->state_notifications == NOTIFICATION_TX_STATE) {
 | 
				
			||||||
 | 
				
			|||||||
@ -140,6 +140,13 @@ SubGhz* subghz_alloc() {
 | 
				
			|||||||
        SubGhzViewFrequencyAnalyzer,
 | 
					        SubGhzViewFrequencyAnalyzer,
 | 
				
			||||||
        subghz_frequency_analyzer_get_view(subghz->subghz_frequency_analyzer));
 | 
					        subghz_frequency_analyzer_get_view(subghz->subghz_frequency_analyzer));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Read RAW
 | 
				
			||||||
 | 
					    subghz->subghz_read_raw = subghz_read_raw_alloc();
 | 
				
			||||||
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
					        subghz->view_dispatcher,
 | 
				
			||||||
 | 
					        SubGhzViewReadRAW,
 | 
				
			||||||
 | 
					        subghz_read_raw_get_view(subghz->subghz_read_raw));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Carrier Test Module
 | 
					    // Carrier Test Module
 | 
				
			||||||
    subghz->subghz_test_carrier = subghz_test_carrier_alloc();
 | 
					    subghz->subghz_test_carrier = subghz_test_carrier_alloc();
 | 
				
			||||||
    view_dispatcher_add_view(
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
@ -167,6 +174,7 @@ SubGhz* subghz_alloc() {
 | 
				
			|||||||
    subghz->txrx->preset = FuriHalSubGhzPresetOok650Async;
 | 
					    subghz->txrx->preset = FuriHalSubGhzPresetOok650Async;
 | 
				
			||||||
    subghz->txrx->txrx_state = SubGhzTxRxStateSleep;
 | 
					    subghz->txrx->txrx_state = SubGhzTxRxStateSleep;
 | 
				
			||||||
    subghz->txrx->hopper_state = SubGhzHopperStateOFF;
 | 
					    subghz->txrx->hopper_state = SubGhzHopperStateOFF;
 | 
				
			||||||
 | 
					    subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
 | 
				
			||||||
    subghz->txrx->history = subghz_history_alloc();
 | 
					    subghz->txrx->history = subghz_history_alloc();
 | 
				
			||||||
    subghz->txrx->worker = subghz_worker_alloc();
 | 
					    subghz->txrx->worker = subghz_worker_alloc();
 | 
				
			||||||
    subghz->txrx->parser = subghz_parser_alloc();
 | 
					    subghz->txrx->parser = subghz_parser_alloc();
 | 
				
			||||||
@ -181,6 +189,7 @@ SubGhz* subghz_alloc() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    subghz_parser_load_keeloq_file(subghz->txrx->parser, "/ext/subghz/keeloq_mfcodes");
 | 
					    subghz_parser_load_keeloq_file(subghz->txrx->parser, "/ext/subghz/keeloq_mfcodes");
 | 
				
			||||||
    subghz_parser_load_nice_flor_s_file(subghz->txrx->parser, "/ext/subghz/nice_floor_s_rx");
 | 
					    subghz_parser_load_nice_flor_s_file(subghz->txrx->parser, "/ext/subghz/nice_floor_s_rx");
 | 
				
			||||||
 | 
					    subghz_parser_load_came_atomo_file(subghz->txrx->parser, "/ext/subghz/came_atomo");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //subghz_parser_enable_dump_text(subghz->protocol, subghz_text_callback, subghz);
 | 
					    //subghz_parser_enable_dump_text(subghz->protocol, subghz_text_callback, subghz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -226,6 +235,10 @@ void subghz_free(SubGhz* subghz) {
 | 
				
			|||||||
    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewFrequencyAnalyzer);
 | 
					    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewFrequencyAnalyzer);
 | 
				
			||||||
    subghz_frequency_analyzer_free(subghz->subghz_frequency_analyzer);
 | 
					    subghz_frequency_analyzer_free(subghz->subghz_frequency_analyzer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Read RAW
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewReadRAW);
 | 
				
			||||||
 | 
					    subghz_read_raw_free(subghz->subghz_read_raw);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Submenu
 | 
					    // Submenu
 | 
				
			||||||
    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewMenu);
 | 
					    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewMenu);
 | 
				
			||||||
    submenu_free(subghz->submenu);
 | 
					    submenu_free(subghz->submenu);
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ void subghz_begin(SubGhz* subghz, FuriHalSubGhzPreset preset) {
 | 
				
			|||||||
    furi_hal_subghz_idle();
 | 
					    furi_hal_subghz_idle();
 | 
				
			||||||
    furi_hal_subghz_load_preset(preset);
 | 
					    furi_hal_subghz_load_preset(preset);
 | 
				
			||||||
    hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
 | 
					    hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
 | 
				
			||||||
    subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
 | 
					    subghz->txrx->txrx_state = SubGhzTxRxStateIDLE;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) {
 | 
					uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) {
 | 
				
			||||||
@ -59,7 +59,7 @@ void subghz_idle(SubGhz* subghz) {
 | 
				
			|||||||
    furi_assert(subghz);
 | 
					    furi_assert(subghz);
 | 
				
			||||||
    furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep);
 | 
					    furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep);
 | 
				
			||||||
    furi_hal_subghz_idle();
 | 
					    furi_hal_subghz_idle();
 | 
				
			||||||
    subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
 | 
					    subghz->txrx->txrx_state = SubGhzTxRxStateIDLE;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_rx_end(SubGhz* subghz) {
 | 
					void subghz_rx_end(SubGhz* subghz) {
 | 
				
			||||||
@ -70,7 +70,7 @@ void subghz_rx_end(SubGhz* subghz) {
 | 
				
			|||||||
        furi_hal_subghz_stop_async_rx();
 | 
					        furi_hal_subghz_stop_async_rx();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    furi_hal_subghz_idle();
 | 
					    furi_hal_subghz_idle();
 | 
				
			||||||
    subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
 | 
					    subghz->txrx->txrx_state = SubGhzTxRxStateIDLE;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_sleep(SubGhz* subghz) {
 | 
					void subghz_sleep(SubGhz* subghz) {
 | 
				
			||||||
@ -191,7 +191,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) {
 | 
				
			|||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if(!subghz->txrx->protocol_result->to_load_protocol_from_file(
 | 
					        if(!subghz->txrx->protocol_result->to_load_protocol_from_file(
 | 
				
			||||||
               file_worker, subghz->txrx->protocol_result)) {
 | 
					               file_worker, subghz->txrx->protocol_result, string_get_cstr(path))) {
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        loaded = true;
 | 
					        loaded = true;
 | 
				
			||||||
@ -208,6 +208,30 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) {
 | 
				
			|||||||
    return loaded;
 | 
					    return loaded;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool subghz_get_next_name_file(SubGhz* subghz) {
 | 
				
			||||||
 | 
					    furi_assert(subghz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FileWorker* file_worker = file_worker_alloc(false);
 | 
				
			||||||
 | 
					    string_t temp_str;
 | 
				
			||||||
 | 
					    string_init(temp_str);
 | 
				
			||||||
 | 
					    bool res = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(strcmp(subghz->file_name, "")) {
 | 
				
			||||||
 | 
					        //get the name of the next free file
 | 
				
			||||||
 | 
					        file_worker_get_next_filename(
 | 
				
			||||||
 | 
					            file_worker, SUBGHZ_RAW_PATH_FOLDER, subghz->file_name, SUBGHZ_APP_EXTENSION, temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        memcpy(subghz->file_name, string_get_cstr(temp_str), strlen(string_get_cstr(temp_str)));
 | 
				
			||||||
 | 
					        res = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    string_clear(temp_str);
 | 
				
			||||||
 | 
					    file_worker_close(file_worker);
 | 
				
			||||||
 | 
					    file_worker_free(file_worker);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool subghz_save_protocol_to_file(SubGhz* subghz, const char* dev_name) {
 | 
					bool subghz_save_protocol_to_file(SubGhz* subghz, const char* dev_name) {
 | 
				
			||||||
    furi_assert(subghz);
 | 
					    furi_assert(subghz);
 | 
				
			||||||
    furi_assert(subghz->txrx->protocol_result);
 | 
					    furi_assert(subghz->txrx->protocol_result);
 | 
				
			||||||
@ -334,8 +358,10 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) {
 | 
				
			|||||||
        if(subghz->txrx->protocol_result == NULL) {
 | 
					        if(subghz->txrx->protocol_result == NULL) {
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if(!subghz->txrx->protocol_result->to_load_protocol_from_file(
 | 
					
 | 
				
			||||||
               file_worker, subghz->txrx->protocol_result)) {
 | 
					        if(subghz->txrx->protocol_result->to_load_protocol_from_file == NULL ||
 | 
				
			||||||
 | 
					           !subghz->txrx->protocol_result->to_load_protocol_from_file(
 | 
				
			||||||
 | 
					               file_worker, subghz->txrx->protocol_result, string_get_cstr(protocol_file_name))) {
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        res = true;
 | 
					        res = true;
 | 
				
			||||||
@ -354,6 +380,28 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) {
 | 
				
			|||||||
    return res;
 | 
					    return res;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool subghz_rename_file(SubGhz* subghz) {
 | 
				
			||||||
 | 
					    furi_assert(subghz);
 | 
				
			||||||
 | 
					    bool ret = false;
 | 
				
			||||||
 | 
					    string_t old_path;
 | 
				
			||||||
 | 
					    string_t new_path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FileWorker* file_worker = file_worker_alloc(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    string_init_printf(
 | 
				
			||||||
 | 
					        old_path, "%s/%s%s", SUBGHZ_APP_PATH_FOLDER, subghz->file_name_tmp, SUBGHZ_APP_EXTENSION);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    string_init_printf(
 | 
				
			||||||
 | 
					        new_path, "%s/%s%s", SUBGHZ_APP_PATH_FOLDER, subghz->file_name, SUBGHZ_APP_EXTENSION);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ret = file_worker_rename(file_worker, string_get_cstr(old_path), string_get_cstr(new_path));
 | 
				
			||||||
 | 
					    string_clear(old_path);
 | 
				
			||||||
 | 
					    string_clear(new_path);
 | 
				
			||||||
 | 
					    file_worker_close(file_worker);
 | 
				
			||||||
 | 
					    file_worker_free(file_worker);
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool subghz_delete_file(SubGhz* subghz) {
 | 
					bool subghz_delete_file(SubGhz* subghz) {
 | 
				
			||||||
    furi_assert(subghz);
 | 
					    furi_assert(subghz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -442,7 +490,7 @@ void subghz_hopper_update(SubGhz* subghz) {
 | 
				
			|||||||
    if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
					    if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
				
			||||||
        subghz_rx_end(subghz);
 | 
					        subghz_rx_end(subghz);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) {
 | 
					    if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) {
 | 
				
			||||||
        subghz_parser_reset(subghz->txrx->parser);
 | 
					        subghz_parser_reset(subghz->txrx->parser);
 | 
				
			||||||
        subghz->txrx->frequency = subghz_hopper_frequencies[subghz->txrx->hopper_idx_frequency];
 | 
					        subghz->txrx->frequency = subghz_hopper_frequencies[subghz->txrx->hopper_idx_frequency];
 | 
				
			||||||
        subghz_rx(subghz, subghz->txrx->frequency);
 | 
					        subghz_rx(subghz, subghz->txrx->frequency);
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@
 | 
				
			|||||||
#include "views/subghz_receiver.h"
 | 
					#include "views/subghz_receiver.h"
 | 
				
			||||||
#include "views/subghz_transmitter.h"
 | 
					#include "views/subghz_transmitter.h"
 | 
				
			||||||
#include "views/subghz_frequency_analyzer.h"
 | 
					#include "views/subghz_frequency_analyzer.h"
 | 
				
			||||||
 | 
					#include "views/subghz_read_raw.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "views/subghz_test_static.h"
 | 
					#include "views/subghz_test_static.h"
 | 
				
			||||||
#include "views/subghz_test_carrier.h"
 | 
					#include "views/subghz_test_carrier.h"
 | 
				
			||||||
@ -46,7 +47,7 @@ extern const uint32_t subghz_frequencies_433_92;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/** SubGhzTxRx state */
 | 
					/** SubGhzTxRx state */
 | 
				
			||||||
typedef enum {
 | 
					typedef enum {
 | 
				
			||||||
    SubGhzTxRxStateIdle,
 | 
					    SubGhzTxRxStateIDLE,
 | 
				
			||||||
    SubGhzTxRxStateRx,
 | 
					    SubGhzTxRxStateRx,
 | 
				
			||||||
    SubGhzTxRxStateTx,
 | 
					    SubGhzTxRxStateTx,
 | 
				
			||||||
    SubGhzTxRxStateSleep,
 | 
					    SubGhzTxRxStateSleep,
 | 
				
			||||||
@ -60,6 +61,15 @@ typedef enum {
 | 
				
			|||||||
    SubGhzHopperStateRSSITimeOut,
 | 
					    SubGhzHopperStateRSSITimeOut,
 | 
				
			||||||
} SubGhzHopperState;
 | 
					} SubGhzHopperState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** SubGhzRxKeyState state */
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    SubGhzRxKeyStateIDLE,
 | 
				
			||||||
 | 
					    SubGhzRxKeyStateNoSave,
 | 
				
			||||||
 | 
					    SubGhzRxKeyStateNeedSave,
 | 
				
			||||||
 | 
					    SubGhzRxKeyStateAddKey,
 | 
				
			||||||
 | 
					    SubGhzRxKeyStateExit,
 | 
				
			||||||
 | 
					} SubGhzRxKeyState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct SubGhzTxRx {
 | 
					struct SubGhzTxRx {
 | 
				
			||||||
    SubGhzWorker* worker;
 | 
					    SubGhzWorker* worker;
 | 
				
			||||||
    SubGhzParser* parser;
 | 
					    SubGhzParser* parser;
 | 
				
			||||||
@ -70,10 +80,10 @@ struct SubGhzTxRx {
 | 
				
			|||||||
    SubGhzHistory* history;
 | 
					    SubGhzHistory* history;
 | 
				
			||||||
    uint16_t idx_menu_chosen;
 | 
					    uint16_t idx_menu_chosen;
 | 
				
			||||||
    SubGhzTxRxState txrx_state;
 | 
					    SubGhzTxRxState txrx_state;
 | 
				
			||||||
    //bool hopper_runing;
 | 
					 | 
				
			||||||
    SubGhzHopperState hopper_state;
 | 
					    SubGhzHopperState hopper_state;
 | 
				
			||||||
    uint8_t hopper_timeout;
 | 
					    uint8_t hopper_timeout;
 | 
				
			||||||
    uint8_t hopper_idx_frequency;
 | 
					    uint8_t hopper_idx_frequency;
 | 
				
			||||||
 | 
					    SubGhzRxKeyState rx_key_state;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct SubGhzTxRx SubGhzTxRx;
 | 
					typedef struct SubGhzTxRx SubGhzTxRx;
 | 
				
			||||||
@ -100,6 +110,7 @@ struct SubGhz {
 | 
				
			|||||||
    VariableItemList* variable_item_list;
 | 
					    VariableItemList* variable_item_list;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SubghzFrequencyAnalyzer* subghz_frequency_analyzer;
 | 
					    SubghzFrequencyAnalyzer* subghz_frequency_analyzer;
 | 
				
			||||||
 | 
					    SubghzReadRAW* subghz_read_raw;
 | 
				
			||||||
    SubghzTestStatic* subghz_test_static;
 | 
					    SubghzTestStatic* subghz_test_static;
 | 
				
			||||||
    SubghzTestCarrier* subghz_test_carrier;
 | 
					    SubghzTestCarrier* subghz_test_carrier;
 | 
				
			||||||
    SubghzTestPacket* subghz_test_packet;
 | 
					    SubghzTestPacket* subghz_test_packet;
 | 
				
			||||||
@ -116,6 +127,7 @@ typedef enum {
 | 
				
			|||||||
    SubGhzViewTransmitter,
 | 
					    SubGhzViewTransmitter,
 | 
				
			||||||
    SubGhzViewVariableItemList,
 | 
					    SubGhzViewVariableItemList,
 | 
				
			||||||
    SubGhzViewFrequencyAnalyzer,
 | 
					    SubGhzViewFrequencyAnalyzer,
 | 
				
			||||||
 | 
					    SubGhzViewReadRAW,
 | 
				
			||||||
    SubGhzViewStatic,
 | 
					    SubGhzViewStatic,
 | 
				
			||||||
    SubGhzViewTestCarrier,
 | 
					    SubGhzViewTestCarrier,
 | 
				
			||||||
    SubGhzViewTestPacket,
 | 
					    SubGhzViewTestPacket,
 | 
				
			||||||
@ -128,8 +140,10 @@ void subghz_sleep(SubGhz* subghz);
 | 
				
			|||||||
bool subghz_tx_start(SubGhz* subghz);
 | 
					bool subghz_tx_start(SubGhz* subghz);
 | 
				
			||||||
void subghz_tx_stop(SubGhz* subghz);
 | 
					void subghz_tx_stop(SubGhz* subghz);
 | 
				
			||||||
bool subghz_key_load(SubGhz* subghz, const char* file_path);
 | 
					bool subghz_key_load(SubGhz* subghz, const char* file_path);
 | 
				
			||||||
 | 
					bool subghz_get_next_name_file(SubGhz* subghz);
 | 
				
			||||||
bool subghz_save_protocol_to_file(SubGhz* subghz, const char* dev_name);
 | 
					bool subghz_save_protocol_to_file(SubGhz* subghz, const char* dev_name);
 | 
				
			||||||
bool subghz_load_protocol_from_file(SubGhz* subghz);
 | 
					bool subghz_load_protocol_from_file(SubGhz* subghz);
 | 
				
			||||||
 | 
					bool subghz_rename_file(SubGhz* subghz);
 | 
				
			||||||
bool subghz_delete_file(SubGhz* subghz);
 | 
					bool subghz_delete_file(SubGhz* subghz);
 | 
				
			||||||
void subghz_file_name_clear(SubGhz* subghz);
 | 
					void subghz_file_name_clear(SubGhz* subghz);
 | 
				
			||||||
uint32_t subghz_random_serial(void);
 | 
					uint32_t subghz_random_serial(void);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,14 +1,11 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <gui/view.h>
 | 
					#include <gui/view.h>
 | 
				
			||||||
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
typedef enum {
 | 
					 | 
				
			||||||
    SubghzFrequencyAnalyzerEventOnlyRx,
 | 
					 | 
				
			||||||
} SubghzFrequencyAnalyzerEvent;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct SubghzFrequencyAnalyzer SubghzFrequencyAnalyzer;
 | 
					typedef struct SubghzFrequencyAnalyzer SubghzFrequencyAnalyzer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef void (*SubghzFrequencyAnalyzerCallback)(SubghzFrequencyAnalyzerEvent event, void* context);
 | 
					typedef void (*SubghzFrequencyAnalyzerCallback)(SubghzCustomEvent event, void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_frequency_analyzer_set_callback(
 | 
					void subghz_frequency_analyzer_set_callback(
 | 
				
			||||||
    SubghzFrequencyAnalyzer* subghz_frequency_analyzer,
 | 
					    SubghzFrequencyAnalyzer* subghz_frequency_analyzer,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										274
									
								
								applications/subghz/views/subghz_read_raw.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								applications/subghz/views/subghz_read_raw.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,274 @@
 | 
				
			|||||||
 | 
					#include "subghz_read_raw.h"
 | 
				
			||||||
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <math.h>
 | 
				
			||||||
 | 
					#include <furi.h>
 | 
				
			||||||
 | 
					#include <furi-hal.h>
 | 
				
			||||||
 | 
					#include <input/input.h>
 | 
				
			||||||
 | 
					#include <gui/elements.h>
 | 
				
			||||||
 | 
					#include <lib/subghz/protocols/subghz_protocol_princeton.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <assets_icons.h>
 | 
				
			||||||
 | 
					#define SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE 100
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    SubghzReadRAWStatusStart,
 | 
				
			||||||
 | 
					    SubghzReadRAWStatusIDLE,
 | 
				
			||||||
 | 
					    SubghzReadRAWStatusREC,
 | 
				
			||||||
 | 
					    //SubghzReadRAWStatusShowName,
 | 
				
			||||||
 | 
					} SubghzReadRAWStatus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct SubghzReadRAW {
 | 
				
			||||||
 | 
					    View* view;
 | 
				
			||||||
 | 
					    SubghzReadRAWCallback callback;
 | 
				
			||||||
 | 
					    void* context;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    string_t frequency_str;
 | 
				
			||||||
 | 
					    string_t preset_str;
 | 
				
			||||||
 | 
					    string_t sample_write;
 | 
				
			||||||
 | 
					    uint8_t* rssi_history;
 | 
				
			||||||
 | 
					    bool rssi_history_end;
 | 
				
			||||||
 | 
					    uint8_t ind_write;
 | 
				
			||||||
 | 
					    SubghzReadRAWStatus satus;
 | 
				
			||||||
 | 
					} SubghzReadRAWModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_set_callback(
 | 
				
			||||||
 | 
					    SubghzReadRAW* subghz_read_raw,
 | 
				
			||||||
 | 
					    SubghzReadRAWCallback callback,
 | 
				
			||||||
 | 
					    void* context) {
 | 
				
			||||||
 | 
					    furi_assert(subghz_read_raw);
 | 
				
			||||||
 | 
					    furi_assert(callback);
 | 
				
			||||||
 | 
					    subghz_read_raw->callback = callback;
 | 
				
			||||||
 | 
					    subghz_read_raw->context = context;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_add_data_statusbar(
 | 
				
			||||||
 | 
					    SubghzReadRAW* instance,
 | 
				
			||||||
 | 
					    const char* frequency_str,
 | 
				
			||||||
 | 
					    const char* preset_str) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        instance->view, (SubghzReadRAWModel * model) {
 | 
				
			||||||
 | 
					            string_set(model->frequency_str, frequency_str);
 | 
				
			||||||
 | 
					            string_set(model->preset_str, preset_str);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_add_data_rssi(SubghzReadRAW* instance, float rssi) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    uint8_t u_rssi = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(rssi < -90) {
 | 
				
			||||||
 | 
					        u_rssi = 0;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        u_rssi = (uint8_t)((rssi + 90) / 2.7);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    //if(u_rssi > 34) u_rssi = 34;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        instance->view, (SubghzReadRAWModel * model) {
 | 
				
			||||||
 | 
					            model->rssi_history[model->ind_write++] = u_rssi;
 | 
				
			||||||
 | 
					            if(model->ind_write > SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) {
 | 
				
			||||||
 | 
					                model->rssi_history_end = true;
 | 
				
			||||||
 | 
					                model->ind_write = 0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_update_sample_write(SubghzReadRAW* instance, size_t sample) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        instance->view, (SubghzReadRAWModel * model) {
 | 
				
			||||||
 | 
					            string_printf(model->sample_write, "%d spl.", sample);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_draw_rssi(Canvas* canvas, SubghzReadRAWModel* model) {
 | 
				
			||||||
 | 
					    int ind = 0;
 | 
				
			||||||
 | 
					    int base = 0;
 | 
				
			||||||
 | 
					    if(model->rssi_history_end == false) {
 | 
				
			||||||
 | 
					        for(int i = model->ind_write; i >= 0; i--) {
 | 
				
			||||||
 | 
					            canvas_draw_line(canvas, i, 47, i, 47 - model->rssi_history[i]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(model->ind_write > 3) {
 | 
				
			||||||
 | 
					            canvas_draw_line(canvas, model->ind_write, 47, model->ind_write, 13);
 | 
				
			||||||
 | 
					            canvas_draw_line(canvas, model->ind_write - 2, 12, model->ind_write + 2, 12);
 | 
				
			||||||
 | 
					            canvas_draw_line(canvas, model->ind_write - 1, 13, model->ind_write + 1, 13);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        base = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - model->ind_write;
 | 
				
			||||||
 | 
					        for(int i = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE; i >= 0; i--) {
 | 
				
			||||||
 | 
					            ind = i - base;
 | 
				
			||||||
 | 
					            if(ind < 0) ind += SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE;
 | 
				
			||||||
 | 
					            canvas_draw_line(canvas, i, 47, i, 47 - model->rssi_history[ind]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        canvas_draw_line(
 | 
				
			||||||
 | 
					            canvas, SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE, 47, SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE, 13);
 | 
				
			||||||
 | 
					        canvas_draw_line(
 | 
				
			||||||
 | 
					            canvas,
 | 
				
			||||||
 | 
					            SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 2,
 | 
				
			||||||
 | 
					            12,
 | 
				
			||||||
 | 
					            SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE + 2,
 | 
				
			||||||
 | 
					            12);
 | 
				
			||||||
 | 
					        canvas_draw_line(
 | 
				
			||||||
 | 
					            canvas,
 | 
				
			||||||
 | 
					            SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 1,
 | 
				
			||||||
 | 
					            13,
 | 
				
			||||||
 | 
					            SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE + 1,
 | 
				
			||||||
 | 
					            13);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_draw(Canvas* canvas, SubghzReadRAWModel* model) {
 | 
				
			||||||
 | 
					    canvas_set_color(canvas, ColorBlack);
 | 
				
			||||||
 | 
					    canvas_set_font(canvas, FontSecondary);
 | 
				
			||||||
 | 
					    canvas_draw_str(canvas, 5, 8, string_get_cstr(model->frequency_str));
 | 
				
			||||||
 | 
					    canvas_draw_str(canvas, 40, 8, string_get_cstr(model->preset_str));
 | 
				
			||||||
 | 
					    canvas_draw_str_aligned(
 | 
				
			||||||
 | 
					        canvas, 126, 0, AlignRight, AlignTop, string_get_cstr(model->sample_write));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canvas_draw_line(canvas, 0, 14, 115, 14);
 | 
				
			||||||
 | 
					    subghz_read_raw_draw_rssi(canvas, model);
 | 
				
			||||||
 | 
					    canvas_draw_line(canvas, 0, 48, 115, 48);
 | 
				
			||||||
 | 
					    canvas_draw_line(canvas, 115, 14, 115, 48);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(model->satus == SubghzReadRAWStatusIDLE) {
 | 
				
			||||||
 | 
					        elements_button_left(canvas, "Config");
 | 
				
			||||||
 | 
					        elements_button_center(canvas, "REC");
 | 
				
			||||||
 | 
					        elements_button_right(canvas, "More");
 | 
				
			||||||
 | 
					    } else if(model->satus == SubghzReadRAWStatusStart) {
 | 
				
			||||||
 | 
					        elements_button_left(canvas, "Config");
 | 
				
			||||||
 | 
					        elements_button_center(canvas, "REC");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        elements_button_center(canvas, "Stop");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canvas_set_font_direction(canvas, 3);
 | 
				
			||||||
 | 
					    canvas_draw_str(canvas, 126, 40, "RSSI");
 | 
				
			||||||
 | 
					    canvas_set_font_direction(canvas, 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool subghz_read_raw_input(InputEvent* event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    SubghzReadRAW* instance = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event->key == InputKeyBack && event->type == InputTypeShort) {
 | 
				
			||||||
 | 
					        instance->callback(SubghzCustomEventViewReadRAWBack, instance->context);
 | 
				
			||||||
 | 
					    } else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
 | 
				
			||||||
 | 
					        with_view_model(
 | 
				
			||||||
 | 
					            instance->view, (SubghzReadRAWModel * model) {
 | 
				
			||||||
 | 
					                if(model->satus == SubghzReadRAWStatusIDLE ||
 | 
				
			||||||
 | 
					                   model->satus == SubghzReadRAWStatusStart) {
 | 
				
			||||||
 | 
					                    instance->callback(SubghzCustomEventViewReadRAWConfig, instance->context);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    } else if(event->key == InputKeyRight && event->type == InputTypeShort) {
 | 
				
			||||||
 | 
					        with_view_model(
 | 
				
			||||||
 | 
					            instance->view, (SubghzReadRAWModel * model) {
 | 
				
			||||||
 | 
					                if(model->satus == SubghzReadRAWStatusIDLE) {
 | 
				
			||||||
 | 
					                    instance->callback(SubghzCustomEventViewReadRAWMore, instance->context);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
 | 
				
			||||||
 | 
					        with_view_model(
 | 
				
			||||||
 | 
					            instance->view, (SubghzReadRAWModel * model) {
 | 
				
			||||||
 | 
					                if(model->satus == SubghzReadRAWStatusIDLE ||
 | 
				
			||||||
 | 
					                   model->satus == SubghzReadRAWStatusStart) {
 | 
				
			||||||
 | 
					                    instance->callback(SubghzCustomEventViewReadRAWREC, instance->context);
 | 
				
			||||||
 | 
					                    model->satus = SubghzReadRAWStatusREC;
 | 
				
			||||||
 | 
					                    model->ind_write = 0;
 | 
				
			||||||
 | 
					                    model->rssi_history_end = false;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    instance->callback(SubghzCustomEventViewReadRAWIDLE, instance->context);
 | 
				
			||||||
 | 
					                    model->satus = SubghzReadRAWStatusIDLE;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_enter(void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    SubghzReadRAW* instance = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        instance->view, (SubghzReadRAWModel * model) {
 | 
				
			||||||
 | 
					            model->satus = SubghzReadRAWStatusStart;
 | 
				
			||||||
 | 
					            model->rssi_history = furi_alloc(SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE * sizeof(uint8_t));
 | 
				
			||||||
 | 
					            model->rssi_history_end = false;
 | 
				
			||||||
 | 
					            model->ind_write = 0;
 | 
				
			||||||
 | 
					            string_set(model->sample_write, "0 spl.");
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_exit(void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    SubghzReadRAW* instance = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        instance->view, (SubghzReadRAWModel * model) {
 | 
				
			||||||
 | 
					            if(model->satus != SubghzReadRAWStatusIDLE &&
 | 
				
			||||||
 | 
					               model->satus != SubghzReadRAWStatusStart) {
 | 
				
			||||||
 | 
					                instance->callback(SubghzCustomEventViewReadRAWIDLE, instance->context);
 | 
				
			||||||
 | 
					                model->satus = SubghzReadRAWStatusStart;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            string_clean(model->frequency_str);
 | 
				
			||||||
 | 
					            string_clean(model->preset_str);
 | 
				
			||||||
 | 
					            string_clean(model->sample_write);
 | 
				
			||||||
 | 
					            free(model->rssi_history);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SubghzReadRAW* subghz_read_raw_alloc() {
 | 
				
			||||||
 | 
					    SubghzReadRAW* instance = furi_alloc(sizeof(SubghzReadRAW));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // View allocation and configuration
 | 
				
			||||||
 | 
					    instance->view = view_alloc();
 | 
				
			||||||
 | 
					    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubghzReadRAWModel));
 | 
				
			||||||
 | 
					    view_set_context(instance->view, instance);
 | 
				
			||||||
 | 
					    view_set_draw_callback(instance->view, (ViewDrawCallback)subghz_read_raw_draw);
 | 
				
			||||||
 | 
					    view_set_input_callback(instance->view, subghz_read_raw_input);
 | 
				
			||||||
 | 
					    view_set_enter_callback(instance->view, subghz_read_raw_enter);
 | 
				
			||||||
 | 
					    view_set_exit_callback(instance->view, subghz_read_raw_exit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        instance->view, (SubghzReadRAWModel * model) {
 | 
				
			||||||
 | 
					            string_init(model->frequency_str);
 | 
				
			||||||
 | 
					            string_init(model->preset_str);
 | 
				
			||||||
 | 
					            string_init(model->sample_write);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_free(SubghzReadRAW* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with_view_model(
 | 
				
			||||||
 | 
					        instance->view, (SubghzReadRAWModel * model) {
 | 
				
			||||||
 | 
					            string_clear(model->frequency_str);
 | 
				
			||||||
 | 
					            string_clear(model->preset_str);
 | 
				
			||||||
 | 
					            string_clear(model->sample_write);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    view_free(instance->view);
 | 
				
			||||||
 | 
					    free(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					View* subghz_read_raw_get_view(SubghzReadRAW* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    return instance->view;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								applications/subghz/views/subghz_read_raw.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								applications/subghz/views/subghz_read_raw.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <gui/view.h>
 | 
				
			||||||
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct SubghzReadRAW SubghzReadRAW;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef void (*SubghzReadRAWCallback)(SubghzCustomEvent event, void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_set_callback(
 | 
				
			||||||
 | 
					    SubghzReadRAW* subghz_read_raw,
 | 
				
			||||||
 | 
					    SubghzReadRAWCallback callback,
 | 
				
			||||||
 | 
					    void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SubghzReadRAW* subghz_read_raw_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_free(SubghzReadRAW* subghz_static);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_add_data_statusbar(
 | 
				
			||||||
 | 
					    SubghzReadRAW* instance,
 | 
				
			||||||
 | 
					    const char* frequency_str,
 | 
				
			||||||
 | 
					    const char* preset_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_update_sample_write(SubghzReadRAW* instance, size_t sample);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void subghz_read_raw_add_data_rssi(SubghzReadRAW* instance, float rssi);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					View* subghz_read_raw_get_view(SubghzReadRAW* subghz_static);
 | 
				
			||||||
@ -181,7 +181,7 @@ bool subghz_receiver_input(InputEvent* event, void* context) {
 | 
				
			|||||||
    SubghzReceiver* subghz_receiver = context;
 | 
					    SubghzReceiver* subghz_receiver = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(event->key == InputKeyBack && event->type == InputTypeShort) {
 | 
					    if(event->key == InputKeyBack && event->type == InputTypeShort) {
 | 
				
			||||||
        subghz_receiver->callback(SubghzReceverEventBack, subghz_receiver->context);
 | 
					        subghz_receiver->callback(SubghzCustomEventViewReceverBack, subghz_receiver->context);
 | 
				
			||||||
    } else if(
 | 
					    } else if(
 | 
				
			||||||
        event->key == InputKeyUp &&
 | 
					        event->key == InputKeyUp &&
 | 
				
			||||||
        (event->type == InputTypeShort || event->type == InputTypeRepeat)) {
 | 
					        (event->type == InputTypeShort || event->type == InputTypeRepeat)) {
 | 
				
			||||||
@ -199,12 +199,13 @@ bool subghz_receiver_input(InputEvent* event, void* context) {
 | 
				
			|||||||
                return true;
 | 
					                return true;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
    } else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
 | 
					    } else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
 | 
				
			||||||
        subghz_receiver->callback(SubghzReceverEventConfig, subghz_receiver->context);
 | 
					        subghz_receiver->callback(SubghzCustomEventViewReceverConfig, subghz_receiver->context);
 | 
				
			||||||
    } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
 | 
					    } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
 | 
				
			||||||
        with_view_model(
 | 
					        with_view_model(
 | 
				
			||||||
            subghz_receiver->view, (SubghzReceiverModel * model) {
 | 
					            subghz_receiver->view, (SubghzReceiverModel * model) {
 | 
				
			||||||
                if(model->history_item != 0) {
 | 
					                if(model->history_item != 0) {
 | 
				
			||||||
                    subghz_receiver->callback(SubghzReceverEventOK, subghz_receiver->context);
 | 
					                    subghz_receiver->callback(
 | 
				
			||||||
 | 
					                        SubghzCustomEventViewReceverOK, subghz_receiver->context);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,11 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <gui/view.h>
 | 
					#include <gui/view.h>
 | 
				
			||||||
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
typedef enum {
 | 
					 | 
				
			||||||
    SubghzReceverEventOK,
 | 
					 | 
				
			||||||
    SubghzReceverEventConfig,
 | 
					 | 
				
			||||||
    SubghzReceverEventBack,
 | 
					 | 
				
			||||||
} SubghzReceverEvent;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct SubghzReceiver SubghzReceiver;
 | 
					typedef struct SubghzReceiver SubghzReceiver;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef void (*SubghzReceiverCallback)(SubghzReceverEvent event, void* context);
 | 
					typedef void (*SubghzReceiverCallback)(SubghzCustomEvent event, void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_receiver_set_callback(
 | 
					void subghz_receiver_set_callback(
 | 
				
			||||||
    SubghzReceiver* subghz_receiver,
 | 
					    SubghzReceiver* subghz_receiver,
 | 
				
			||||||
 | 
				
			|||||||
@ -111,10 +111,12 @@ bool subghz_transmitter_input(InputEvent* event, void* context) {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(can_be_sent && event->key == InputKeyOk && event->type == InputTypePress) {
 | 
					    if(can_be_sent && event->key == InputKeyOk && event->type == InputTypePress) {
 | 
				
			||||||
        subghz_transmitter->callback(SubghzTransmitterEventSendStart, subghz_transmitter->context);
 | 
					        subghz_transmitter->callback(
 | 
				
			||||||
 | 
					            SubghzCustomEventViewTransmitterSendStart, subghz_transmitter->context);
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    } else if(can_be_sent && event->key == InputKeyOk && event->type == InputTypeRelease) {
 | 
					    } else if(can_be_sent && event->key == InputKeyOk && event->type == InputTypeRelease) {
 | 
				
			||||||
        subghz_transmitter->callback(SubghzTransmitterEventSendStop, subghz_transmitter->context);
 | 
					        subghz_transmitter->callback(
 | 
				
			||||||
 | 
					            SubghzCustomEventViewTransmitterSendStop, subghz_transmitter->context);
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +1,11 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <gui/view.h>
 | 
					#include <gui/view.h>
 | 
				
			||||||
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
typedef enum {
 | 
					 | 
				
			||||||
    SubghzTransmitterEventSendStart,
 | 
					 | 
				
			||||||
    SubghzTransmitterEventSendStop,
 | 
					 | 
				
			||||||
    SubghzTransmitterEventBack,
 | 
					 | 
				
			||||||
    SubghzTransmitterEventNoMan,
 | 
					 | 
				
			||||||
} SubghzTransmitterEvent;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct SubghzTransmitter SubghzTransmitter;
 | 
					typedef struct SubghzTransmitter SubghzTransmitter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef void (*SubghzTransmitterCallback)(SubghzTransmitterEvent event, void* context);
 | 
					typedef void (*SubghzTransmitterCallback)(SubghzCustomEvent event, void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_transmitter_set_callback(
 | 
					void subghz_transmitter_set_callback(
 | 
				
			||||||
    SubghzTransmitter* subghz_transmitter,
 | 
					    SubghzTransmitter* subghz_transmitter,
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,10 @@ void test_furi_create_open() {
 | 
				
			|||||||
    // 2. Open it
 | 
					    // 2. Open it
 | 
				
			||||||
    void* record = furi_record_open("test/holding");
 | 
					    void* record = furi_record_open("test/holding");
 | 
				
			||||||
    mu_assert_pointers_eq(record, &test_data);
 | 
					    mu_assert_pointers_eq(record, &test_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 3. Close it
 | 
				
			||||||
    furi_record_close("test/holding");
 | 
					    furi_record_close("test/holding");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 4. Clean up
 | 
				
			||||||
    furi_record_destroy("test/holding");
 | 
					    furi_record_destroy("test/holding");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -325,7 +325,6 @@ MU_TEST_SUITE(test_irda_decoder_encoder) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
int run_minunit_test_irda_decoder_encoder() {
 | 
					int run_minunit_test_irda_decoder_encoder() {
 | 
				
			||||||
    MU_RUN_SUITE(test_irda_decoder_encoder);
 | 
					    MU_RUN_SUITE(test_irda_decoder_encoder);
 | 
				
			||||||
    MU_REPORT();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return MU_EXIT_CODE;
 | 
					    return MU_EXIT_CODE;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -79,6 +79,9 @@ extern "C" {
 | 
				
			|||||||
__attribute__((unused)) static void (*minunit_setup)(void) = NULL;
 | 
					__attribute__((unused)) static void (*minunit_setup)(void) = NULL;
 | 
				
			||||||
__attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
					__attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void minunit_print_progress(void);
 | 
				
			||||||
 | 
					void minunit_print_fail(const char* error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*  Definitions */
 | 
					/*  Definitions */
 | 
				
			||||||
#define MU_TEST(method_name) static void method_name(void)
 | 
					#define MU_TEST(method_name) static void method_name(void)
 | 
				
			||||||
#define MU_TEST_SUITE(suite_name) static void suite_name(void)
 | 
					#define MU_TEST_SUITE(suite_name) static void suite_name(void)
 | 
				
			||||||
@ -108,8 +111,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
        minunit_run++;                                           \
 | 
					        minunit_run++;                                           \
 | 
				
			||||||
        if(minunit_status) {                                     \
 | 
					        if(minunit_status) {                                     \
 | 
				
			||||||
            minunit_fail++;                                      \
 | 
					            minunit_fail++;                                      \
 | 
				
			||||||
            printf("F");                                         \
 | 
					            minunit_print_fail(minunit_last_message);            \
 | 
				
			||||||
            printf("\n%s\n", minunit_last_message);              \
 | 
					 | 
				
			||||||
        } fflush(stdout);                                        \
 | 
					        } fflush(stdout);                                        \
 | 
				
			||||||
        if(minunit_teardown)(*minunit_teardown)();)
 | 
					        if(minunit_teardown)(*minunit_teardown)();)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -142,7 +144,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                #test);                    \
 | 
					                #test);                    \
 | 
				
			||||||
            minunit_status = 1;            \
 | 
					            minunit_status = 1;            \
 | 
				
			||||||
            return;                        \
 | 
					            return;                        \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_fail(message)                          \
 | 
					#define mu_fail(message)                          \
 | 
				
			||||||
    MU__SAFE_BLOCK(minunit_assert++; snprintf(    \
 | 
					    MU__SAFE_BLOCK(minunit_assert++; snprintf(    \
 | 
				
			||||||
@ -169,7 +171,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                message);                  \
 | 
					                message);                  \
 | 
				
			||||||
            minunit_status = 1;            \
 | 
					            minunit_status = 1;            \
 | 
				
			||||||
            return;                        \
 | 
					            return;                        \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_int_eq(expected, result)                                                  \
 | 
					#define mu_assert_int_eq(expected, result)                                                  \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                         \
 | 
					    MU__SAFE_BLOCK(                                                                         \
 | 
				
			||||||
@ -187,7 +189,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                minunit_tmp_r);                                                             \
 | 
					                minunit_tmp_r);                                                             \
 | 
				
			||||||
            minunit_status = 1;                                                             \
 | 
					            minunit_status = 1;                                                             \
 | 
				
			||||||
            return;                                                                         \
 | 
					            return;                                                                         \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_int_not_eq(expected, result)                                              \
 | 
					#define mu_assert_int_not_eq(expected, result)                                              \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                         \
 | 
					    MU__SAFE_BLOCK(                                                                         \
 | 
				
			||||||
@ -204,7 +206,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                minunit_tmp_e);                                                             \
 | 
					                minunit_tmp_e);                                                             \
 | 
				
			||||||
            minunit_status = 1;                                                             \
 | 
					            minunit_status = 1;                                                             \
 | 
				
			||||||
            return;                                                                         \
 | 
					            return;                                                                         \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_int_greater_than(val, result)                                        \
 | 
					#define mu_assert_int_greater_than(val, result)                                        \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                    \
 | 
					    MU__SAFE_BLOCK(                                                                    \
 | 
				
			||||||
@ -222,7 +224,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                minunit_tmp_e);                                                        \
 | 
					                minunit_tmp_e);                                                        \
 | 
				
			||||||
            minunit_status = 1;                                                        \
 | 
					            minunit_status = 1;                                                        \
 | 
				
			||||||
            return;                                                                    \
 | 
					            return;                                                                    \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_int_less_than(val, result)                                           \
 | 
					#define mu_assert_int_less_than(val, result)                                           \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                    \
 | 
					    MU__SAFE_BLOCK(                                                                    \
 | 
				
			||||||
@ -240,7 +242,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                minunit_tmp_e);                                                        \
 | 
					                minunit_tmp_e);                                                        \
 | 
				
			||||||
            minunit_status = 1;                                                        \
 | 
					            minunit_status = 1;                                                        \
 | 
				
			||||||
            return;                                                                    \
 | 
					            return;                                                                    \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_int_between(expected_lower, expected_upper, result)              \
 | 
					#define mu_assert_int_between(expected_lower, expected_upper, result)              \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                \
 | 
					    MU__SAFE_BLOCK(                                                                \
 | 
				
			||||||
@ -261,7 +263,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                minunit_tmp_m);                                                    \
 | 
					                minunit_tmp_m);                                                    \
 | 
				
			||||||
            minunit_status = 1;                                                    \
 | 
					            minunit_status = 1;                                                    \
 | 
				
			||||||
            return;                                                                \
 | 
					            return;                                                                \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_int_in(expected, array_length, result)                                 \
 | 
					#define mu_assert_int_in(expected, array_length, result)                                 \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                      \
 | 
					    MU__SAFE_BLOCK(                                                                      \
 | 
				
			||||||
@ -288,7 +290,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                minunit_tmp_r);                                                          \
 | 
					                minunit_tmp_r);                                                          \
 | 
				
			||||||
            minunit_status = 1;                                                          \
 | 
					            minunit_status = 1;                                                          \
 | 
				
			||||||
            return;                                                                      \
 | 
					            return;                                                                      \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_double_eq(expected, result)                                                     \
 | 
					#define mu_assert_double_eq(expected, result)                                                     \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                               \
 | 
					    MU__SAFE_BLOCK(                                                                               \
 | 
				
			||||||
@ -309,7 +311,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                minunit_tmp_r);                                                                   \
 | 
					                minunit_tmp_r);                                                                   \
 | 
				
			||||||
            minunit_status = 1;                                                                   \
 | 
					            minunit_status = 1;                                                                   \
 | 
				
			||||||
            return;                                                                               \
 | 
					            return;                                                                               \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_double_greater_than(val, result)                                           \
 | 
					#define mu_assert_double_greater_than(val, result)                                           \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                          \
 | 
					    MU__SAFE_BLOCK(                                                                          \
 | 
				
			||||||
@ -327,7 +329,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                minunit_tmp_e);                                                              \
 | 
					                minunit_tmp_e);                                                              \
 | 
				
			||||||
            minunit_status = 1;                                                              \
 | 
					            minunit_status = 1;                                                              \
 | 
				
			||||||
            return;                                                                          \
 | 
					            return;                                                                          \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_double_less_than(val, result)                                              \
 | 
					#define mu_assert_double_less_than(val, result)                                              \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                          \
 | 
					    MU__SAFE_BLOCK(                                                                          \
 | 
				
			||||||
@ -345,7 +347,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                minunit_tmp_e);                                                              \
 | 
					                minunit_tmp_e);                                                              \
 | 
				
			||||||
            minunit_status = 1;                                                              \
 | 
					            minunit_status = 1;                                                              \
 | 
				
			||||||
            return;                                                                          \
 | 
					            return;                                                                          \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_double_between(expected_lower, expected_upper, result)                    \
 | 
					#define mu_assert_double_between(expected_lower, expected_upper, result)                    \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                         \
 | 
					    MU__SAFE_BLOCK(                                                                         \
 | 
				
			||||||
@ -366,7 +368,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                minunit_tmp_m);                                                             \
 | 
					                minunit_tmp_m);                                                             \
 | 
				
			||||||
            minunit_status = 1;                                                             \
 | 
					            minunit_status = 1;                                                             \
 | 
				
			||||||
            return;                                                                         \
 | 
					            return;                                                                         \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_string_eq(expected, result)                                         \
 | 
					#define mu_assert_string_eq(expected, result)                                         \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                   \
 | 
					    MU__SAFE_BLOCK(                                                                   \
 | 
				
			||||||
@ -386,39 +388,39 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
                minunit_tmp_r);                                                       \
 | 
					                minunit_tmp_r);                                                       \
 | 
				
			||||||
            minunit_status = 1;                                                       \
 | 
					            minunit_status = 1;                                                       \
 | 
				
			||||||
            return;                                                                   \
 | 
					            return;                                                                   \
 | 
				
			||||||
        } else { printf("."); })
 | 
					        } else { minunit_print_progress(); })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_null(result)                                       \
 | 
					#define mu_assert_null(result)                                                    \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                  \
 | 
					    MU__SAFE_BLOCK(                                                               \
 | 
				
			||||||
        minunit_assert++; if(result == NULL) { printf("."); } else { \
 | 
					        minunit_assert++; if(result == NULL) { minunit_print_progress(); } else { \
 | 
				
			||||||
            snprintf(                                                \
 | 
					            snprintf(                                                             \
 | 
				
			||||||
                minunit_last_message,                                \
 | 
					                minunit_last_message,                                             \
 | 
				
			||||||
                MINUNIT_MESSAGE_LEN,                                 \
 | 
					                MINUNIT_MESSAGE_LEN,                                              \
 | 
				
			||||||
                "%s failed:\n\t%s:%d: Expected result was not NULL", \
 | 
					                "%s failed:\n\t%s:%d: Expected result was not NULL",              \
 | 
				
			||||||
                __func__,                                            \
 | 
					                __func__,                                                         \
 | 
				
			||||||
                __FILE__,                                            \
 | 
					                __FILE__,                                                         \
 | 
				
			||||||
                __LINE__);                                           \
 | 
					                __LINE__);                                                        \
 | 
				
			||||||
            minunit_status = 1;                                      \
 | 
					            minunit_status = 1;                                                   \
 | 
				
			||||||
            return;                                                  \
 | 
					            return;                                                               \
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_not_null(result)                                   \
 | 
					#define mu_assert_not_null(result)                                                \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                  \
 | 
					    MU__SAFE_BLOCK(                                                               \
 | 
				
			||||||
        minunit_assert++; if(result != NULL) { printf("."); } else { \
 | 
					        minunit_assert++; if(result != NULL) { minunit_print_progress(); } else { \
 | 
				
			||||||
            snprintf(                                                \
 | 
					            snprintf(                                                             \
 | 
				
			||||||
                minunit_last_message,                                \
 | 
					                minunit_last_message,                                             \
 | 
				
			||||||
                MINUNIT_MESSAGE_LEN,                                 \
 | 
					                MINUNIT_MESSAGE_LEN,                                              \
 | 
				
			||||||
                "%s failed:\n\t%s:%d: Expected result was not NULL", \
 | 
					                "%s failed:\n\t%s:%d: Expected result was not NULL",              \
 | 
				
			||||||
                __func__,                                            \
 | 
					                __func__,                                                         \
 | 
				
			||||||
                __FILE__,                                            \
 | 
					                __FILE__,                                                         \
 | 
				
			||||||
                __LINE__);                                           \
 | 
					                __LINE__);                                                        \
 | 
				
			||||||
            minunit_status = 1;                                      \
 | 
					            minunit_status = 1;                                                   \
 | 
				
			||||||
            return;                                                  \
 | 
					            return;                                                               \
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define mu_assert_pointers_eq(pointer1, pointer2)                                                  \
 | 
					#define mu_assert_pointers_eq(pointer1, pointer2)                                                  \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                                \
 | 
					    MU__SAFE_BLOCK(                                                                                \
 | 
				
			||||||
        minunit_assert++; if(pointer1 == pointer2) { printf("."); } else {                         \
 | 
					        minunit_assert++; if(pointer1 == pointer2) { minunit_print_progress(); } else {            \
 | 
				
			||||||
            snprintf(                                                                              \
 | 
					            snprintf(                                                                              \
 | 
				
			||||||
                minunit_last_message,                                                              \
 | 
					                minunit_last_message,                                                              \
 | 
				
			||||||
                MINUNIT_MESSAGE_LEN,                                                               \
 | 
					                MINUNIT_MESSAGE_LEN,                                                               \
 | 
				
			||||||
@ -432,7 +434,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#define mu_assert_pointers_not_eq(pointer1, pointer2)                                              \
 | 
					#define mu_assert_pointers_not_eq(pointer1, pointer2)                                              \
 | 
				
			||||||
    MU__SAFE_BLOCK(                                                                                \
 | 
					    MU__SAFE_BLOCK(                                                                                \
 | 
				
			||||||
        minunit_assert++; if(pointer1 != pointer2) { printf("."); } else {                         \
 | 
					        minunit_assert++; if(pointer1 != pointer2) { minunit_print_progress(); } else {            \
 | 
				
			||||||
            snprintf(                                                                              \
 | 
					            snprintf(                                                                              \
 | 
				
			||||||
                minunit_last_message,                                                              \
 | 
					                minunit_last_message,                                                              \
 | 
				
			||||||
                MINUNIT_MESSAGE_LEN,                                                               \
 | 
					                MINUNIT_MESSAGE_LEN,                                                               \
 | 
				
			||||||
@ -603,4 +605,4 @@ __attribute__((unused)) static double mu_timer_cpu(void) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* MINUNIT_MINUNIT_H */
 | 
					#endif /* MINUNIT_MINUNIT_H */
 | 
				
			||||||
 | 
				
			|||||||
@ -62,7 +62,6 @@ MU_TEST_SUITE(test_suite) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
int run_minunit() {
 | 
					int run_minunit() {
 | 
				
			||||||
    MU_RUN_SUITE(test_suite);
 | 
					    MU_RUN_SUITE(test_suite);
 | 
				
			||||||
    MU_REPORT();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return MU_EXIT_CODE;
 | 
					    return MU_EXIT_CODE;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -56,25 +56,40 @@ static void test_rpc_compare_messages(PB_Main* result, PB_Main* expected);
 | 
				
			|||||||
static void test_rpc_decode_and_compare(MsgList_t expected_msg_list);
 | 
					static void test_rpc_decode_and_compare(MsgList_t expected_msg_list);
 | 
				
			||||||
static void test_rpc_free_msg_list(MsgList_t msg_list);
 | 
					static void test_rpc_free_msg_list(MsgList_t msg_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void test_rpc_storage_setup(void) {
 | 
					static void test_rpc_setup(void) {
 | 
				
			||||||
    furi_assert(!rpc);
 | 
					    furi_assert(!rpc);
 | 
				
			||||||
    furi_assert(!session);
 | 
					    furi_assert(!session);
 | 
				
			||||||
    furi_assert(!output_stream);
 | 
					    furi_assert(!output_stream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    rpc = furi_record_open("rpc");
 | 
					    rpc = furi_record_open("rpc");
 | 
				
			||||||
    for(int i = 0; !session && (i < 10000); ++i) {
 | 
					    for(int i = 0; !session && (i < 10000); ++i) {
 | 
				
			||||||
        session = rpc_open_session(rpc);
 | 
					        session = rpc_session_open(rpc);
 | 
				
			||||||
        delay(1);
 | 
					        delay(1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    furi_assert(session);
 | 
					    furi_assert(session);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    output_stream = xStreamBufferCreate(1000, 1);
 | 
				
			||||||
 | 
					    mu_assert(session, "failed to start session");
 | 
				
			||||||
 | 
					    rpc_session_set_send_bytes_callback(session, output_bytes_callback);
 | 
				
			||||||
 | 
					    rpc_session_set_context(session, output_stream);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void test_rpc_teardown(void) {
 | 
				
			||||||
 | 
					    rpc_session_close(session);
 | 
				
			||||||
 | 
					    furi_record_close("rpc");
 | 
				
			||||||
 | 
					    vStreamBufferDelete(output_stream);
 | 
				
			||||||
 | 
					    ++command_id;
 | 
				
			||||||
 | 
					    output_stream = NULL;
 | 
				
			||||||
 | 
					    rpc = NULL;
 | 
				
			||||||
 | 
					    session = NULL;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void test_rpc_storage_setup(void) {
 | 
				
			||||||
 | 
					    test_rpc_setup();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Storage* fs_api = furi_record_open("storage");
 | 
					    Storage* fs_api = furi_record_open("storage");
 | 
				
			||||||
    clean_directory(fs_api, TEST_DIR_NAME);
 | 
					    clean_directory(fs_api, TEST_DIR_NAME);
 | 
				
			||||||
    furi_record_close("storage");
 | 
					    furi_record_close("storage");
 | 
				
			||||||
 | 
					 | 
				
			||||||
    output_stream = xStreamBufferCreate(1000, 1);
 | 
					 | 
				
			||||||
    mu_assert(session, "failed to start session");
 | 
					 | 
				
			||||||
    rpc_set_send_bytes_callback(session, output_bytes_callback, output_stream);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void test_rpc_storage_teardown(void) {
 | 
					static void test_rpc_storage_teardown(void) {
 | 
				
			||||||
@ -82,13 +97,7 @@ static void test_rpc_storage_teardown(void) {
 | 
				
			|||||||
    clean_directory(fs_api, TEST_DIR_NAME);
 | 
					    clean_directory(fs_api, TEST_DIR_NAME);
 | 
				
			||||||
    furi_record_close("storage");
 | 
					    furi_record_close("storage");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    rpc_close_session(session);
 | 
					    test_rpc_teardown();
 | 
				
			||||||
    furi_record_close("rpc");
 | 
					 | 
				
			||||||
    vStreamBufferDelete(output_stream);
 | 
					 | 
				
			||||||
    ++command_id;
 | 
					 | 
				
			||||||
    output_stream = NULL;
 | 
					 | 
				
			||||||
    rpc = NULL;
 | 
					 | 
				
			||||||
    session = NULL;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void clean_directory(Storage* fs_api, const char* clean_dir) {
 | 
					static void clean_directory(Storage* fs_api, const char* clean_dir) {
 | 
				
			||||||
@ -197,10 +206,12 @@ static void test_rpc_create_simple_message(
 | 
				
			|||||||
    const char* str,
 | 
					    const char* str,
 | 
				
			||||||
    uint32_t command_id) {
 | 
					    uint32_t command_id) {
 | 
				
			||||||
    furi_assert(message);
 | 
					    furi_assert(message);
 | 
				
			||||||
    furi_assert(str);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    char* str_copy = furi_alloc(strlen(str) + 1);
 | 
					    char* str_copy = NULL;
 | 
				
			||||||
    strcpy(str_copy, str);
 | 
					    if(str) {
 | 
				
			||||||
 | 
					        str_copy = furi_alloc(strlen(str) + 1);
 | 
				
			||||||
 | 
					        strcpy(str_copy, str);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    message->command_id = command_id;
 | 
					    message->command_id = command_id;
 | 
				
			||||||
    message->command_status = PB_CommandStatus_OK;
 | 
					    message->command_status = PB_CommandStatus_OK;
 | 
				
			||||||
    message->cb_content.funcs.encode = NULL;
 | 
					    message->cb_content.funcs.encode = NULL;
 | 
				
			||||||
@ -292,7 +303,7 @@ static void test_rpc_encode_and_feed_one(PB_Main* request) {
 | 
				
			|||||||
    size_t bytes_left = ostream.bytes_written;
 | 
					    size_t bytes_left = ostream.bytes_written;
 | 
				
			||||||
    uint8_t* buffer_ptr = buffer;
 | 
					    uint8_t* buffer_ptr = buffer;
 | 
				
			||||||
    do {
 | 
					    do {
 | 
				
			||||||
        size_t bytes_sent = rpc_feed_bytes(session, buffer_ptr, bytes_left, 1000);
 | 
					        size_t bytes_sent = rpc_session_feed(session, buffer_ptr, bytes_left, 1000);
 | 
				
			||||||
        mu_check(bytes_sent > 0);
 | 
					        mu_check(bytes_sent > 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bytes_left -= bytes_sent;
 | 
					        bytes_left -= bytes_sent;
 | 
				
			||||||
@ -402,6 +413,38 @@ static bool test_rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_
 | 
				
			|||||||
    return (count == bytes_received);
 | 
					    return (count == bytes_received);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					    test_rpc_storage_list_create_expected_list_root(MsgList_t msg_list, uint32_t command_id) {
 | 
				
			||||||
 | 
					    PB_Main* message = MsgList_push_new(msg_list);
 | 
				
			||||||
 | 
					    message->has_next = false;
 | 
				
			||||||
 | 
					    message->cb_content.funcs.encode = NULL;
 | 
				
			||||||
 | 
					    message->command_id = command_id;
 | 
				
			||||||
 | 
					    message->which_content = PB_Main_storage_list_response_tag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file_count = 3;
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file[0].data = NULL;
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file[1].data = NULL;
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file[2].data = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file[0].size = 0;
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file[1].size = 0;
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file[2].size = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file[0].type = PB_Storage_File_FileType_DIR;
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file[1].type = PB_Storage_File_FileType_DIR;
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file[2].type = PB_Storage_File_FileType_DIR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    char* str = furi_alloc(4);
 | 
				
			||||||
 | 
					    strcpy(str, "any");
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file[0].name = str;
 | 
				
			||||||
 | 
					    str = furi_alloc(4);
 | 
				
			||||||
 | 
					    strcpy(str, "int");
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file[1].name = str;
 | 
				
			||||||
 | 
					    str = furi_alloc(4);
 | 
				
			||||||
 | 
					    strcpy(str, "ext");
 | 
				
			||||||
 | 
					    message->content.storage_list_response.file[2].name = str;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void test_rpc_storage_list_create_expected_list(
 | 
					static void test_rpc_storage_list_create_expected_list(
 | 
				
			||||||
    MsgList_t msg_list,
 | 
					    MsgList_t msg_list,
 | 
				
			||||||
    const char* path,
 | 
					    const char* path,
 | 
				
			||||||
@ -505,7 +548,11 @@ static void test_rpc_storage_list_run(const char* path, uint32_t command_id) {
 | 
				
			|||||||
    MsgList_init(expected_msg_list);
 | 
					    MsgList_init(expected_msg_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_create_simple_message(&request, PB_Main_storage_list_request_tag, path, command_id);
 | 
					    test_rpc_create_simple_message(&request, PB_Main_storage_list_request_tag, path, command_id);
 | 
				
			||||||
    test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id);
 | 
					    if(!strcmp(path, "/")) {
 | 
				
			||||||
 | 
					        test_rpc_storage_list_create_expected_list_root(expected_msg_list, command_id);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    test_rpc_encode_and_feed_one(&request);
 | 
					    test_rpc_encode_and_feed_one(&request);
 | 
				
			||||||
    test_rpc_decode_and_compare(expected_msg_list);
 | 
					    test_rpc_decode_and_compare(expected_msg_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -514,6 +561,7 @@ static void test_rpc_storage_list_run(const char* path, uint32_t command_id) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MU_TEST(test_storage_list) {
 | 
					MU_TEST(test_storage_list) {
 | 
				
			||||||
 | 
					    test_rpc_storage_list_run("/", ++command_id);
 | 
				
			||||||
    test_rpc_storage_list_run("/ext/nfc", ++command_id);
 | 
					    test_rpc_storage_list_run("/ext/nfc", ++command_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_storage_list_run("/int", ++command_id);
 | 
					    test_rpc_storage_list_run("/int", ++command_id);
 | 
				
			||||||
@ -597,12 +645,23 @@ static void test_storage_read_run(const char* path, uint32_t command_id) {
 | 
				
			|||||||
    test_rpc_free_msg_list(expected_msg_list);
 | 
					    test_rpc_free_msg_list(expected_msg_list);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool test_is_exists(const char* path) {
 | 
				
			||||||
 | 
					    Storage* fs_api = furi_record_open("storage");
 | 
				
			||||||
 | 
					    FileInfo fileinfo;
 | 
				
			||||||
 | 
					    FS_Error result = storage_common_stat(fs_api, path, &fileinfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_check((result == FSE_OK) || (result == FSE_NOT_EXIST));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result == FSE_OK;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void test_create_dir(const char* path) {
 | 
					static void test_create_dir(const char* path) {
 | 
				
			||||||
    Storage* fs_api = furi_record_open("storage");
 | 
					    Storage* fs_api = furi_record_open("storage");
 | 
				
			||||||
    FS_Error error = storage_common_mkdir(fs_api, path);
 | 
					    FS_Error error = storage_common_mkdir(fs_api, path);
 | 
				
			||||||
    (void)error;
 | 
					    (void)error;
 | 
				
			||||||
    furi_assert((error == FSE_OK) || (error == FSE_EXIST));
 | 
					    furi_assert((error == FSE_OK) || (error == FSE_EXIST));
 | 
				
			||||||
    furi_record_close("storage");
 | 
					    furi_record_close("storage");
 | 
				
			||||||
 | 
					    furi_check(test_is_exists(path));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void test_create_file(const char* path, size_t size) {
 | 
					static void test_create_file(const char* path, size_t size) {
 | 
				
			||||||
@ -625,6 +684,7 @@ static void test_create_file(const char* path, size_t size) {
 | 
				
			|||||||
    storage_file_free(file);
 | 
					    storage_file_free(file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    furi_record_close("storage");
 | 
					    furi_record_close("storage");
 | 
				
			||||||
 | 
					    furi_check(test_is_exists(path));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MU_TEST(test_storage_read) {
 | 
					MU_TEST(test_storage_read) {
 | 
				
			||||||
@ -829,12 +889,17 @@ MU_TEST(test_storage_interrupt_continuous_another_system) {
 | 
				
			|||||||
    test_rpc_free_msg_list(expected_msg_list);
 | 
					    test_rpc_free_msg_list(expected_msg_list);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void test_storage_delete_run(const char* path, size_t command_id, PB_CommandStatus status) {
 | 
					static void test_storage_delete_run(
 | 
				
			||||||
 | 
					    const char* path,
 | 
				
			||||||
 | 
					    size_t command_id,
 | 
				
			||||||
 | 
					    PB_CommandStatus status,
 | 
				
			||||||
 | 
					    bool recursive) {
 | 
				
			||||||
    PB_Main request;
 | 
					    PB_Main request;
 | 
				
			||||||
    MsgList_t expected_msg_list;
 | 
					    MsgList_t expected_msg_list;
 | 
				
			||||||
    MsgList_init(expected_msg_list);
 | 
					    MsgList_init(expected_msg_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_create_simple_message(&request, PB_Main_storage_delete_request_tag, path, command_id);
 | 
					    test_rpc_create_simple_message(&request, PB_Main_storage_delete_request_tag, path, command_id);
 | 
				
			||||||
 | 
					    request.content.storage_delete_request.recursive = recursive;
 | 
				
			||||||
    test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
 | 
					    test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_encode_and_feed_one(&request);
 | 
					    test_rpc_encode_and_feed_one(&request);
 | 
				
			||||||
@ -844,16 +909,69 @@ static void test_storage_delete_run(const char* path, size_t command_id, PB_Comm
 | 
				
			|||||||
    test_rpc_free_msg_list(expected_msg_list);
 | 
					    test_rpc_free_msg_list(expected_msg_list);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MU_TEST(test_storage_delete) {
 | 
					#define TEST_DIR_RMRF_NAME TEST_DIR "rmrf_test"
 | 
				
			||||||
    test_create_file(TEST_DIR "empty.txt", 0);
 | 
					#define TEST_DIR_RMRF TEST_DIR_RMRF_NAME "/"
 | 
				
			||||||
    test_storage_delete_run(TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_OK);
 | 
					MU_TEST(test_storage_delete_recursive) {
 | 
				
			||||||
    test_storage_delete_run(
 | 
					    test_create_dir(TEST_DIR_RMRF_NAME);
 | 
				
			||||||
        TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_ERROR_STORAGE_NOT_EXIST);
 | 
					
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir1");
 | 
				
			||||||
 | 
					    test_create_file(TEST_DIR_RMRF "dir1/file1", 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir1/dir1");
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir1/dir2");
 | 
				
			||||||
 | 
					    test_create_file(TEST_DIR_RMRF "dir1/dir2/file1", 1);
 | 
				
			||||||
 | 
					    test_create_file(TEST_DIR_RMRF "dir1/dir2/file2", 1);
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir1/dir3");
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1");
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1/dir1");
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1/dir1/dir1");
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1/dir1/dir1/dir1");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir2");
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir2/dir1");
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir2/dir2");
 | 
				
			||||||
 | 
					    test_create_file(TEST_DIR_RMRF "dir2/dir2/file1", 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir2/dir2/dir1");
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir2/dir2/dir1/dir1");
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF "dir2/dir2/dir1/dir1/dir1");
 | 
				
			||||||
 | 
					    test_create_file(TEST_DIR_RMRF "dir2/dir2/dir1/dir1/dir1/file1", 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_create_dir(TEST_DIR "dir1");
 | 
					 | 
				
			||||||
    test_storage_delete_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK);
 | 
					 | 
				
			||||||
    test_storage_delete_run(
 | 
					    test_storage_delete_run(
 | 
				
			||||||
        TEST_DIR "dir1", ++command_id, PB_CommandStatus_ERROR_STORAGE_NOT_EXIST);
 | 
					        TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY, false);
 | 
				
			||||||
 | 
					    mu_check(test_is_exists(TEST_DIR_RMRF_NAME));
 | 
				
			||||||
 | 
					    test_storage_delete_run(TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_OK, true);
 | 
				
			||||||
 | 
					    mu_check(!test_is_exists(TEST_DIR_RMRF_NAME));
 | 
				
			||||||
 | 
					    test_storage_delete_run(TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_OK, false);
 | 
				
			||||||
 | 
					    mu_check(!test_is_exists(TEST_DIR_RMRF_NAME));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR_RMRF_NAME);
 | 
				
			||||||
 | 
					    test_storage_delete_run(TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_OK, true);
 | 
				
			||||||
 | 
					    mu_check(!test_is_exists(TEST_DIR_RMRF_NAME));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR "file1");
 | 
				
			||||||
 | 
					    test_storage_delete_run(TEST_DIR "file1", ++command_id, PB_CommandStatus_OK, true);
 | 
				
			||||||
 | 
					    mu_check(!test_is_exists(TEST_DIR "file1"));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MU_TEST(test_storage_delete) {
 | 
				
			||||||
 | 
					    test_storage_delete_run(NULL, ++command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_check(!test_is_exists(TEST_DIR "empty.txt"));
 | 
				
			||||||
 | 
					    test_storage_delete_run(TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_OK, false);
 | 
				
			||||||
 | 
					    mu_check(!test_is_exists(TEST_DIR "empty.txt"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test_create_file(TEST_DIR "empty.txt", 0);
 | 
				
			||||||
 | 
					    test_storage_delete_run(TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_OK, false);
 | 
				
			||||||
 | 
					    mu_check(!test_is_exists(TEST_DIR "empty.txt"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_check(!test_is_exists(TEST_DIR "dir1"));
 | 
				
			||||||
 | 
					    test_create_dir(TEST_DIR "dir1");
 | 
				
			||||||
 | 
					    test_storage_delete_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK, false);
 | 
				
			||||||
 | 
					    mu_check(!test_is_exists(TEST_DIR "dir1"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test_storage_delete_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK, false);
 | 
				
			||||||
 | 
					    mu_check(!test_is_exists(TEST_DIR "dir1"));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void test_storage_mkdir_run(const char* path, size_t command_id, PB_CommandStatus status) {
 | 
					static void test_storage_mkdir_run(const char* path, size_t command_id, PB_CommandStatus status) {
 | 
				
			||||||
@ -872,18 +990,17 @@ static void test_storage_mkdir_run(const char* path, size_t command_id, PB_Comma
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MU_TEST(test_storage_mkdir) {
 | 
					MU_TEST(test_storage_mkdir) {
 | 
				
			||||||
 | 
					    furi_check(!test_is_exists(TEST_DIR "dir1"));
 | 
				
			||||||
    test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK);
 | 
					    test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK);
 | 
				
			||||||
 | 
					    mu_check(test_is_exists(TEST_DIR "dir1"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_ERROR_STORAGE_EXIST);
 | 
					    test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_ERROR_STORAGE_EXIST);
 | 
				
			||||||
 | 
					    mu_check(test_is_exists(TEST_DIR "dir1"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_check(!test_is_exists(TEST_DIR "dir2"));
 | 
				
			||||||
    test_create_dir(TEST_DIR "dir2");
 | 
					    test_create_dir(TEST_DIR "dir2");
 | 
				
			||||||
    test_storage_mkdir_run(TEST_DIR "dir2", ++command_id, PB_CommandStatus_ERROR_STORAGE_EXIST);
 | 
					    test_storage_mkdir_run(TEST_DIR "dir2", ++command_id, PB_CommandStatus_ERROR_STORAGE_EXIST);
 | 
				
			||||||
 | 
					    mu_check(test_is_exists(TEST_DIR "dir2"));
 | 
				
			||||||
    Storage* fs_api = furi_record_open("storage");
 | 
					 | 
				
			||||||
    FS_Error error = storage_common_remove(fs_api, TEST_DIR "dir1");
 | 
					 | 
				
			||||||
    (void)error;
 | 
					 | 
				
			||||||
    furi_assert(error == FSE_OK);
 | 
					 | 
				
			||||||
    furi_record_close("storage");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void test_storage_calculate_md5sum(const char* path, char* md5sum) {
 | 
					static void test_storage_calculate_md5sum(const char* path, char* md5sum) {
 | 
				
			||||||
@ -1013,7 +1130,7 @@ MU_TEST(test_ping) {
 | 
				
			|||||||
//       4) test for fill buffer till end (great varint) and close connection
 | 
					//       4) test for fill buffer till end (great varint) and close connection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MU_TEST_SUITE(test_rpc_status) {
 | 
					MU_TEST_SUITE(test_rpc_status) {
 | 
				
			||||||
    MU_SUITE_CONFIGURE(&test_rpc_storage_setup, &test_rpc_storage_teardown);
 | 
					    MU_SUITE_CONFIGURE(&test_rpc_setup, &test_rpc_teardown);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MU_RUN_TEST(test_ping);
 | 
					    MU_RUN_TEST(test_ping);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1026,6 +1143,7 @@ MU_TEST_SUITE(test_rpc_storage) {
 | 
				
			|||||||
    MU_RUN_TEST(test_storage_write_read);
 | 
					    MU_RUN_TEST(test_storage_write_read);
 | 
				
			||||||
    MU_RUN_TEST(test_storage_write);
 | 
					    MU_RUN_TEST(test_storage_write);
 | 
				
			||||||
    MU_RUN_TEST(test_storage_delete);
 | 
					    MU_RUN_TEST(test_storage_delete);
 | 
				
			||||||
 | 
					    MU_RUN_TEST(test_storage_delete_recursive);
 | 
				
			||||||
    MU_RUN_TEST(test_storage_mkdir);
 | 
					    MU_RUN_TEST(test_storage_mkdir);
 | 
				
			||||||
    MU_RUN_TEST(test_storage_md5sum);
 | 
					    MU_RUN_TEST(test_storage_md5sum);
 | 
				
			||||||
    MU_RUN_TEST(test_storage_interrupt_continuous_same_system);
 | 
					    MU_RUN_TEST(test_storage_interrupt_continuous_same_system);
 | 
				
			||||||
@ -1112,20 +1230,19 @@ MU_TEST(test_app_start_and_lock_status) {
 | 
				
			|||||||
        "skynet_destroy_world_app", NULL, PB_CommandStatus_ERROR_INVALID_PARAMETERS, ++command_id);
 | 
					        "skynet_destroy_world_app", NULL, PB_CommandStatus_ERROR_INVALID_PARAMETERS, ++command_id);
 | 
				
			||||||
    test_app_get_status_lock_run(false, ++command_id);
 | 
					    test_app_get_status_lock_run(false, ++command_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_app_start_run("Delay Test App", "0", PB_CommandStatus_OK, ++command_id);
 | 
					    test_app_start_run("Delay Test", "0", PB_CommandStatus_OK, ++command_id);
 | 
				
			||||||
    delay(100);
 | 
					    delay(100);
 | 
				
			||||||
    test_app_get_status_lock_run(false, ++command_id);
 | 
					    test_app_get_status_lock_run(false, ++command_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_app_start_run("Delay Test App", "200", PB_CommandStatus_OK, ++command_id);
 | 
					    test_app_start_run("Delay Test", "200", PB_CommandStatus_OK, ++command_id);
 | 
				
			||||||
    test_app_get_status_lock_run(true, ++command_id);
 | 
					    test_app_get_status_lock_run(true, ++command_id);
 | 
				
			||||||
    delay(100);
 | 
					    delay(100);
 | 
				
			||||||
    test_app_get_status_lock_run(true, ++command_id);
 | 
					    test_app_get_status_lock_run(true, ++command_id);
 | 
				
			||||||
    test_app_start_run(
 | 
					    test_app_start_run("Delay Test", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id);
 | 
				
			||||||
        "Delay Test App", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id);
 | 
					 | 
				
			||||||
    delay(200);
 | 
					    delay(200);
 | 
				
			||||||
    test_app_get_status_lock_run(false, ++command_id);
 | 
					    test_app_get_status_lock_run(false, ++command_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_app_start_run("Delay Test App", "500", PB_CommandStatus_OK, ++command_id);
 | 
					    test_app_start_run("Delay Test", "500", PB_CommandStatus_OK, ++command_id);
 | 
				
			||||||
    delay(100);
 | 
					    delay(100);
 | 
				
			||||||
    test_app_get_status_lock_run(true, ++command_id);
 | 
					    test_app_get_status_lock_run(true, ++command_id);
 | 
				
			||||||
    test_app_start_run("Infrared", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id);
 | 
					    test_app_start_run("Infrared", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id);
 | 
				
			||||||
@ -1140,16 +1257,22 @@ MU_TEST(test_app_start_and_lock_status) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MU_TEST_SUITE(test_rpc_app) {
 | 
					MU_TEST_SUITE(test_rpc_app) {
 | 
				
			||||||
    MU_SUITE_CONFIGURE(&test_rpc_storage_setup, &test_rpc_storage_teardown);
 | 
					    MU_SUITE_CONFIGURE(&test_rpc_setup, &test_rpc_teardown);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MU_RUN_TEST(test_app_start_and_lock_status);
 | 
					    MU_RUN_TEST(test_app_start_and_lock_status);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int run_minunit_test_rpc() {
 | 
					int run_minunit_test_rpc() {
 | 
				
			||||||
    MU_RUN_SUITE(test_rpc_storage);
 | 
					    Storage* storage = furi_record_open("storage");
 | 
				
			||||||
 | 
					    furi_record_close("storage");
 | 
				
			||||||
 | 
					    if(storage_sd_status(storage) != FSE_OK) {
 | 
				
			||||||
 | 
					        FURI_LOG_E("UNIT_TESTS", "SD card not mounted - skip storage tests");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        MU_RUN_SUITE(test_rpc_storage);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MU_RUN_SUITE(test_rpc_status);
 | 
					    MU_RUN_SUITE(test_rpc_status);
 | 
				
			||||||
    MU_RUN_SUITE(test_rpc_app);
 | 
					    MU_RUN_SUITE(test_rpc_app);
 | 
				
			||||||
    MU_REPORT();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return MU_EXIT_CODE;
 | 
					    return MU_EXIT_CODE;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,10 +7,27 @@
 | 
				
			|||||||
#include <cli/cli.h>
 | 
					#include <cli/cli.h>
 | 
				
			||||||
#include <loader/loader.h>
 | 
					#include <loader/loader.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define TESTS_TAG "UNIT_TESTS"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int run_minunit();
 | 
					int run_minunit();
 | 
				
			||||||
int run_minunit_test_irda_decoder_encoder();
 | 
					int run_minunit_test_irda_decoder_encoder();
 | 
				
			||||||
int run_minunit_test_rpc();
 | 
					int run_minunit_test_rpc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void minunit_print_progress(void) {
 | 
				
			||||||
 | 
					    static char progress[] = {'\\', '|', '/', '-'};
 | 
				
			||||||
 | 
					    static uint8_t progress_counter = 0;
 | 
				
			||||||
 | 
					    static TickType_t last_tick = 0;
 | 
				
			||||||
 | 
					    TickType_t current_tick = xTaskGetTickCount();
 | 
				
			||||||
 | 
					    if(current_tick - last_tick > 20) {
 | 
				
			||||||
 | 
					        last_tick = current_tick;
 | 
				
			||||||
 | 
					        printf("[%c]\033[3D", progress[++progress_counter % COUNT_OF(progress)]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void minunit_print_fail(const char* str) {
 | 
				
			||||||
 | 
					    printf("%s\n", str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void unit_tests_cli(Cli* cli, string_t args, void* context) {
 | 
					void unit_tests_cli(Cli* cli, string_t args, void* context) {
 | 
				
			||||||
    uint32_t test_result = 0;
 | 
					    uint32_t test_result = 0;
 | 
				
			||||||
    minunit_run = 0;
 | 
					    minunit_run = 0;
 | 
				
			||||||
@ -25,21 +42,30 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) {
 | 
				
			|||||||
    furi_record_close("notification");
 | 
					    furi_record_close("notification");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(loader_is_locked(loader)) {
 | 
					    if(loader_is_locked(loader)) {
 | 
				
			||||||
        FURI_LOG_E("UNIT_TESTS", "RPC: stop all applications to run tests");
 | 
					        FURI_LOG_E(TESTS_TAG, "RPC: stop all applications to run tests");
 | 
				
			||||||
        notification_message(notification, &sequence_blink_magenta_100);
 | 
					        notification_message(notification, &sequence_blink_magenta_100);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        notification_message_block(notification, &sequence_set_only_blue_255);
 | 
					        notification_message_block(notification, &sequence_set_only_blue_255);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        uint32_t heap_before = memmgr_get_free_heap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        test_result |= run_minunit();
 | 
					        test_result |= run_minunit();
 | 
				
			||||||
        test_result |= run_minunit_test_irda_decoder_encoder();
 | 
					        test_result |= run_minunit_test_irda_decoder_encoder();
 | 
				
			||||||
        test_result |= run_minunit_test_rpc();
 | 
					        test_result |= run_minunit_test_rpc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(test_result == 0) {
 | 
					        if(test_result == 0) {
 | 
				
			||||||
 | 
					            delay(200); /* wait for tested services and apps to deallocate */
 | 
				
			||||||
 | 
					            uint32_t heap_after = memmgr_get_free_heap();
 | 
				
			||||||
            notification_message(notification, &sequence_success);
 | 
					            notification_message(notification, &sequence_success);
 | 
				
			||||||
            FURI_LOG_I("UNIT_TESTS", "PASSED");
 | 
					            if(heap_after != heap_before) {
 | 
				
			||||||
 | 
					                FURI_LOG_E(TESTS_TAG, "Leaked: %d", heap_before - heap_after);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                FURI_LOG_I(TESTS_TAG, "No leaks");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            FURI_LOG_I(TESTS_TAG, "PASSED");
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            notification_message(notification, &sequence_error);
 | 
					            notification_message(notification, &sequence_error);
 | 
				
			||||||
            FURI_LOG_E("UNIT_TESTS", "FAILED");
 | 
					            FURI_LOG_E(TESTS_TAG, "FAILED");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,9 @@
 | 
				
			|||||||
PB_BIND(PB_Empty, PB_Empty, AUTO)
 | 
					PB_BIND(PB_Empty, PB_Empty, AUTO)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PB_BIND(PB_StopSession, PB_StopSession, AUTO)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PB_BIND(PB_Main, PB_Main, AUTO)
 | 
					PB_BIND(PB_Main, PB_Main, AUTO)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -30,7 +30,8 @@ typedef enum _PB_CommandStatus {
 | 
				
			|||||||
    PB_CommandStatus_ERROR_STORAGE_INTERNAL = 11, /* *< Internal error */
 | 
					    PB_CommandStatus_ERROR_STORAGE_INTERNAL = 11, /* *< Internal error */
 | 
				
			||||||
    PB_CommandStatus_ERROR_STORAGE_NOT_IMPLEMENTED = 12, /* *< Functon not implemented */
 | 
					    PB_CommandStatus_ERROR_STORAGE_NOT_IMPLEMENTED = 12, /* *< Functon not implemented */
 | 
				
			||||||
    PB_CommandStatus_ERROR_STORAGE_ALREADY_OPEN = 13, /* *< File/Dir already opened */
 | 
					    PB_CommandStatus_ERROR_STORAGE_ALREADY_OPEN = 13, /* *< File/Dir already opened */
 | 
				
			||||||
    PB_CommandStatus_ERROR_APP_CANT_START = 16, /* *< Can't start app - or internal error */
 | 
					    PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY = 18, /* *< Directory, you're going to remove is not empty */
 | 
				
			||||||
 | 
					    PB_CommandStatus_ERROR_APP_CANT_START = 16, /* *< Can't start app - internal error */
 | 
				
			||||||
    PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED = 17 /* *< Another app is running */
 | 
					    PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED = 17 /* *< Another app is running */
 | 
				
			||||||
} PB_CommandStatus;
 | 
					} PB_CommandStatus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -42,6 +43,10 @@ typedef struct _PB_Empty {
 | 
				
			|||||||
    char dummy_field;
 | 
					    char dummy_field;
 | 
				
			||||||
} PB_Empty;
 | 
					} PB_Empty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct _PB_StopSession { 
 | 
				
			||||||
 | 
					    char dummy_field;
 | 
				
			||||||
 | 
					} PB_StopSession;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct _PB_Main { 
 | 
					typedef struct _PB_Main { 
 | 
				
			||||||
    uint32_t command_id; 
 | 
					    uint32_t command_id; 
 | 
				
			||||||
    PB_CommandStatus command_status; 
 | 
					    PB_CommandStatus command_status; 
 | 
				
			||||||
@ -64,14 +69,15 @@ typedef struct _PB_Main {
 | 
				
			|||||||
        PB_App_Start app_start;
 | 
					        PB_App_Start app_start;
 | 
				
			||||||
        PB_App_LockStatusRequest app_lock_status_request;
 | 
					        PB_App_LockStatusRequest app_lock_status_request;
 | 
				
			||||||
        PB_App_LockStatusResponse app_lock_status_response;
 | 
					        PB_App_LockStatusResponse app_lock_status_response;
 | 
				
			||||||
 | 
					        PB_StopSession stop_session;
 | 
				
			||||||
    } content; 
 | 
					    } content; 
 | 
				
			||||||
} PB_Main;
 | 
					} PB_Main;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Helper constants for enums */
 | 
					/* Helper constants for enums */
 | 
				
			||||||
#define _PB_CommandStatus_MIN PB_CommandStatus_OK
 | 
					#define _PB_CommandStatus_MIN PB_CommandStatus_OK
 | 
				
			||||||
#define _PB_CommandStatus_MAX PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED
 | 
					#define _PB_CommandStatus_MAX PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY
 | 
				
			||||||
#define _PB_CommandStatus_ARRAYSIZE ((PB_CommandStatus)(PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED+1))
 | 
					#define _PB_CommandStatus_ARRAYSIZE ((PB_CommandStatus)(PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY+1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef __cplusplus
 | 
					#ifdef __cplusplus
 | 
				
			||||||
@ -80,8 +86,10 @@ extern "C" {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/* Initializer values for message structs */
 | 
					/* Initializer values for message structs */
 | 
				
			||||||
#define PB_Empty_init_default                    {0}
 | 
					#define PB_Empty_init_default                    {0}
 | 
				
			||||||
 | 
					#define PB_StopSession_init_default              {0}
 | 
				
			||||||
#define PB_Main_init_default                     {0, _PB_CommandStatus_MIN, 0, {{NULL}, NULL}, 0, {PB_Empty_init_default}}
 | 
					#define PB_Main_init_default                     {0, _PB_CommandStatus_MIN, 0, {{NULL}, NULL}, 0, {PB_Empty_init_default}}
 | 
				
			||||||
#define PB_Empty_init_zero                       {0}
 | 
					#define PB_Empty_init_zero                       {0}
 | 
				
			||||||
 | 
					#define PB_StopSession_init_zero                 {0}
 | 
				
			||||||
#define PB_Main_init_zero                        {0, _PB_CommandStatus_MIN, 0, {{NULL}, NULL}, 0, {PB_Empty_init_zero}}
 | 
					#define PB_Main_init_zero                        {0, _PB_CommandStatus_MIN, 0, {{NULL}, NULL}, 0, {PB_Empty_init_zero}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Field tags (for use in manual encoding/decoding) */
 | 
					/* Field tags (for use in manual encoding/decoding) */
 | 
				
			||||||
@ -103,6 +111,7 @@ extern "C" {
 | 
				
			|||||||
#define PB_Main_app_start_tag                    16
 | 
					#define PB_Main_app_start_tag                    16
 | 
				
			||||||
#define PB_Main_app_lock_status_request_tag      17
 | 
					#define PB_Main_app_lock_status_request_tag      17
 | 
				
			||||||
#define PB_Main_app_lock_status_response_tag     18
 | 
					#define PB_Main_app_lock_status_response_tag     18
 | 
				
			||||||
 | 
					#define PB_Main_stop_session_tag                 19
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Struct field encoding specification for nanopb */
 | 
					/* Struct field encoding specification for nanopb */
 | 
				
			||||||
#define PB_Empty_FIELDLIST(X, a) \
 | 
					#define PB_Empty_FIELDLIST(X, a) \
 | 
				
			||||||
@ -110,6 +119,11 @@ extern "C" {
 | 
				
			|||||||
#define PB_Empty_CALLBACK NULL
 | 
					#define PB_Empty_CALLBACK NULL
 | 
				
			||||||
#define PB_Empty_DEFAULT NULL
 | 
					#define PB_Empty_DEFAULT NULL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define PB_StopSession_FIELDLIST(X, a) \
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define PB_StopSession_CALLBACK NULL
 | 
				
			||||||
 | 
					#define PB_StopSession_DEFAULT NULL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define PB_Main_FIELDLIST(X, a) \
 | 
					#define PB_Main_FIELDLIST(X, a) \
 | 
				
			||||||
X(a, STATIC,   SINGULAR, UINT32,   command_id,        1) \
 | 
					X(a, STATIC,   SINGULAR, UINT32,   command_id,        1) \
 | 
				
			||||||
X(a, STATIC,   SINGULAR, UENUM,    command_status,    2) \
 | 
					X(a, STATIC,   SINGULAR, UENUM,    command_status,    2) \
 | 
				
			||||||
@ -128,7 +142,8 @@ X(a, STATIC,   ONEOF,    MSG_W_CB, (content,storage_md5sum_request,content.stora
 | 
				
			|||||||
X(a, STATIC,   ONEOF,    MSG_W_CB, (content,storage_md5sum_response,content.storage_md5sum_response),  15) \
 | 
					X(a, STATIC,   ONEOF,    MSG_W_CB, (content,storage_md5sum_response,content.storage_md5sum_response),  15) \
 | 
				
			||||||
X(a, STATIC,   ONEOF,    MSG_W_CB, (content,app_start,content.app_start),  16) \
 | 
					X(a, STATIC,   ONEOF,    MSG_W_CB, (content,app_start,content.app_start),  16) \
 | 
				
			||||||
X(a, STATIC,   ONEOF,    MSG_W_CB, (content,app_lock_status_request,content.app_lock_status_request),  17) \
 | 
					X(a, STATIC,   ONEOF,    MSG_W_CB, (content,app_lock_status_request,content.app_lock_status_request),  17) \
 | 
				
			||||||
X(a, STATIC,   ONEOF,    MSG_W_CB, (content,app_lock_status_response,content.app_lock_status_response),  18)
 | 
					X(a, STATIC,   ONEOF,    MSG_W_CB, (content,app_lock_status_response,content.app_lock_status_response),  18) \
 | 
				
			||||||
 | 
					X(a, STATIC,   ONEOF,    MSG_W_CB, (content,stop_session,content.stop_session),  19)
 | 
				
			||||||
#define PB_Main_CALLBACK NULL
 | 
					#define PB_Main_CALLBACK NULL
 | 
				
			||||||
#define PB_Main_DEFAULT NULL
 | 
					#define PB_Main_DEFAULT NULL
 | 
				
			||||||
#define PB_Main_content_empty_MSGTYPE PB_Empty
 | 
					#define PB_Main_content_empty_MSGTYPE PB_Empty
 | 
				
			||||||
@ -146,16 +161,20 @@ X(a, STATIC,   ONEOF,    MSG_W_CB, (content,app_lock_status_response,content.app
 | 
				
			|||||||
#define PB_Main_content_app_start_MSGTYPE PB_App_Start
 | 
					#define PB_Main_content_app_start_MSGTYPE PB_App_Start
 | 
				
			||||||
#define PB_Main_content_app_lock_status_request_MSGTYPE PB_App_LockStatusRequest
 | 
					#define PB_Main_content_app_lock_status_request_MSGTYPE PB_App_LockStatusRequest
 | 
				
			||||||
#define PB_Main_content_app_lock_status_response_MSGTYPE PB_App_LockStatusResponse
 | 
					#define PB_Main_content_app_lock_status_response_MSGTYPE PB_App_LockStatusResponse
 | 
				
			||||||
 | 
					#define PB_Main_content_stop_session_MSGTYPE PB_StopSession
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern const pb_msgdesc_t PB_Empty_msg;
 | 
					extern const pb_msgdesc_t PB_Empty_msg;
 | 
				
			||||||
 | 
					extern const pb_msgdesc_t PB_StopSession_msg;
 | 
				
			||||||
extern const pb_msgdesc_t PB_Main_msg;
 | 
					extern const pb_msgdesc_t PB_Main_msg;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
 | 
					/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
 | 
				
			||||||
#define PB_Empty_fields &PB_Empty_msg
 | 
					#define PB_Empty_fields &PB_Empty_msg
 | 
				
			||||||
 | 
					#define PB_StopSession_fields &PB_StopSession_msg
 | 
				
			||||||
#define PB_Main_fields &PB_Main_msg
 | 
					#define PB_Main_fields &PB_Main_msg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Maximum encoded size of messages (where known) */
 | 
					/* Maximum encoded size of messages (where known) */
 | 
				
			||||||
#define PB_Empty_size                            0
 | 
					#define PB_Empty_size                            0
 | 
				
			||||||
 | 
					#define PB_StopSession_size                      0
 | 
				
			||||||
#if defined(PB_Storage_ListRequest_size) && defined(PB_Storage_ListResponse_size) && defined(PB_Storage_ReadRequest_size) && defined(PB_Storage_ReadResponse_size) && defined(PB_Storage_WriteRequest_size) && defined(PB_Storage_DeleteRequest_size) && defined(PB_Storage_MkdirRequest_size) && defined(PB_Storage_Md5sumRequest_size) && defined(PB_App_Start_size)
 | 
					#if defined(PB_Storage_ListRequest_size) && defined(PB_Storage_ListResponse_size) && defined(PB_Storage_ReadRequest_size) && defined(PB_Storage_ReadResponse_size) && defined(PB_Storage_WriteRequest_size) && defined(PB_Storage_DeleteRequest_size) && defined(PB_Storage_MkdirRequest_size) && defined(PB_Storage_Md5sumRequest_size) && defined(PB_App_Start_size)
 | 
				
			||||||
#define PB_Main_size                             (10 + sizeof(union PB_Main_content_size_union))
 | 
					#define PB_Main_size                             (10 + sizeof(union PB_Main_content_size_union))
 | 
				
			||||||
union PB_Main_content_size_union {char f7[(6 + PB_Storage_ListRequest_size)]; char f8[(6 + PB_Storage_ListResponse_size)]; char f9[(6 + PB_Storage_ReadRequest_size)]; char f10[(6 + PB_Storage_ReadResponse_size)]; char f11[(6 + PB_Storage_WriteRequest_size)]; char f12[(6 + PB_Storage_DeleteRequest_size)]; char f13[(6 + PB_Storage_MkdirRequest_size)]; char f14[(6 + PB_Storage_Md5sumRequest_size)]; char f16[(7 + PB_App_Start_size)]; char f0[36];};
 | 
					union PB_Main_content_size_union {char f7[(6 + PB_Storage_ListRequest_size)]; char f8[(6 + PB_Storage_ListResponse_size)]; char f9[(6 + PB_Storage_ReadRequest_size)]; char f10[(6 + PB_Storage_ReadResponse_size)]; char f11[(6 + PB_Storage_WriteRequest_size)]; char f12[(6 + PB_Storage_DeleteRequest_size)]; char f13[(6 + PB_Storage_MkdirRequest_size)]; char f14[(6 + PB_Storage_Md5sumRequest_size)]; char f16[(7 + PB_App_Start_size)]; char f0[36];};
 | 
				
			||||||
 | 
				
			|||||||
@ -16,10 +16,6 @@ typedef enum _PB_Storage_File_FileType {
 | 
				
			|||||||
} PB_Storage_File_FileType;
 | 
					} PB_Storage_File_FileType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Struct definitions */
 | 
					/* Struct definitions */
 | 
				
			||||||
typedef struct _PB_Storage_DeleteRequest { 
 | 
					 | 
				
			||||||
    char *path; 
 | 
					 | 
				
			||||||
} PB_Storage_DeleteRequest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef struct _PB_Storage_ListRequest { 
 | 
					typedef struct _PB_Storage_ListRequest { 
 | 
				
			||||||
    char *path; 
 | 
					    char *path; 
 | 
				
			||||||
} PB_Storage_ListRequest;
 | 
					} PB_Storage_ListRequest;
 | 
				
			||||||
@ -36,6 +32,11 @@ typedef struct _PB_Storage_ReadRequest {
 | 
				
			|||||||
    char *path; 
 | 
					    char *path; 
 | 
				
			||||||
} PB_Storage_ReadRequest;
 | 
					} PB_Storage_ReadRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct _PB_Storage_DeleteRequest { 
 | 
				
			||||||
 | 
					    char *path; 
 | 
				
			||||||
 | 
					    bool recursive; 
 | 
				
			||||||
 | 
					} PB_Storage_DeleteRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct _PB_Storage_File { 
 | 
					typedef struct _PB_Storage_File { 
 | 
				
			||||||
    PB_Storage_File_FileType type; 
 | 
					    PB_Storage_File_FileType type; 
 | 
				
			||||||
    char *name; 
 | 
					    char *name; 
 | 
				
			||||||
@ -81,7 +82,7 @@ extern "C" {
 | 
				
			|||||||
#define PB_Storage_ReadRequest_init_default      {NULL}
 | 
					#define PB_Storage_ReadRequest_init_default      {NULL}
 | 
				
			||||||
#define PB_Storage_ReadResponse_init_default     {false, PB_Storage_File_init_default}
 | 
					#define PB_Storage_ReadResponse_init_default     {false, PB_Storage_File_init_default}
 | 
				
			||||||
#define PB_Storage_WriteRequest_init_default     {NULL, false, PB_Storage_File_init_default}
 | 
					#define PB_Storage_WriteRequest_init_default     {NULL, false, PB_Storage_File_init_default}
 | 
				
			||||||
#define PB_Storage_DeleteRequest_init_default    {NULL}
 | 
					#define PB_Storage_DeleteRequest_init_default    {NULL, 0}
 | 
				
			||||||
#define PB_Storage_MkdirRequest_init_default     {NULL}
 | 
					#define PB_Storage_MkdirRequest_init_default     {NULL}
 | 
				
			||||||
#define PB_Storage_Md5sumRequest_init_default    {NULL}
 | 
					#define PB_Storage_Md5sumRequest_init_default    {NULL}
 | 
				
			||||||
#define PB_Storage_Md5sumResponse_init_default   {""}
 | 
					#define PB_Storage_Md5sumResponse_init_default   {""}
 | 
				
			||||||
@ -91,17 +92,18 @@ extern "C" {
 | 
				
			|||||||
#define PB_Storage_ReadRequest_init_zero         {NULL}
 | 
					#define PB_Storage_ReadRequest_init_zero         {NULL}
 | 
				
			||||||
#define PB_Storage_ReadResponse_init_zero        {false, PB_Storage_File_init_zero}
 | 
					#define PB_Storage_ReadResponse_init_zero        {false, PB_Storage_File_init_zero}
 | 
				
			||||||
#define PB_Storage_WriteRequest_init_zero        {NULL, false, PB_Storage_File_init_zero}
 | 
					#define PB_Storage_WriteRequest_init_zero        {NULL, false, PB_Storage_File_init_zero}
 | 
				
			||||||
#define PB_Storage_DeleteRequest_init_zero       {NULL}
 | 
					#define PB_Storage_DeleteRequest_init_zero       {NULL, 0}
 | 
				
			||||||
#define PB_Storage_MkdirRequest_init_zero        {NULL}
 | 
					#define PB_Storage_MkdirRequest_init_zero        {NULL}
 | 
				
			||||||
#define PB_Storage_Md5sumRequest_init_zero       {NULL}
 | 
					#define PB_Storage_Md5sumRequest_init_zero       {NULL}
 | 
				
			||||||
#define PB_Storage_Md5sumResponse_init_zero      {""}
 | 
					#define PB_Storage_Md5sumResponse_init_zero      {""}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Field tags (for use in manual encoding/decoding) */
 | 
					/* Field tags (for use in manual encoding/decoding) */
 | 
				
			||||||
#define PB_Storage_DeleteRequest_path_tag        1
 | 
					 | 
				
			||||||
#define PB_Storage_ListRequest_path_tag          1
 | 
					#define PB_Storage_ListRequest_path_tag          1
 | 
				
			||||||
#define PB_Storage_Md5sumRequest_path_tag        1
 | 
					#define PB_Storage_Md5sumRequest_path_tag        1
 | 
				
			||||||
#define PB_Storage_MkdirRequest_path_tag         1
 | 
					#define PB_Storage_MkdirRequest_path_tag         1
 | 
				
			||||||
#define PB_Storage_ReadRequest_path_tag          1
 | 
					#define PB_Storage_ReadRequest_path_tag          1
 | 
				
			||||||
 | 
					#define PB_Storage_DeleteRequest_path_tag        1
 | 
				
			||||||
 | 
					#define PB_Storage_DeleteRequest_recursive_tag   2
 | 
				
			||||||
#define PB_Storage_File_type_tag                 1
 | 
					#define PB_Storage_File_type_tag                 1
 | 
				
			||||||
#define PB_Storage_File_name_tag                 2
 | 
					#define PB_Storage_File_name_tag                 2
 | 
				
			||||||
#define PB_Storage_File_size_tag                 3
 | 
					#define PB_Storage_File_size_tag                 3
 | 
				
			||||||
@ -151,7 +153,8 @@ X(a, STATIC,   OPTIONAL, MESSAGE,  file,              2)
 | 
				
			|||||||
#define PB_Storage_WriteRequest_file_MSGTYPE PB_Storage_File
 | 
					#define PB_Storage_WriteRequest_file_MSGTYPE PB_Storage_File
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define PB_Storage_DeleteRequest_FIELDLIST(X, a) \
 | 
					#define PB_Storage_DeleteRequest_FIELDLIST(X, a) \
 | 
				
			||||||
X(a, POINTER,  SINGULAR, STRING,   path,              1)
 | 
					X(a, POINTER,  SINGULAR, STRING,   path,              1) \
 | 
				
			||||||
 | 
					X(a, STATIC,   SINGULAR, BOOL,     recursive,         2)
 | 
				
			||||||
#define PB_Storage_DeleteRequest_CALLBACK NULL
 | 
					#define PB_Storage_DeleteRequest_CALLBACK NULL
 | 
				
			||||||
#define PB_Storage_DeleteRequest_DEFAULT NULL
 | 
					#define PB_Storage_DeleteRequest_DEFAULT NULL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1 +1 @@
 | 
				
			|||||||
Subproject commit 8e6db414beed5aff0902f2cca2f4146a0dffb7a1
 | 
					Subproject commit 021ba48abb64d25c7094da13b752fe37d4bf6007
 | 
				
			||||||
@ -272,7 +272,7 @@ const struct Version* furi_hal_version_get_firmware_version(void) {
 | 
				
			|||||||
    return version_get();
 | 
					    return version_get();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const struct Version* furi_hal_version_get_boot_version(void) {
 | 
					const struct Version* furi_hal_version_get_bootloader_version(void) {
 | 
				
			||||||
#ifdef NO_BOOTLOADER
 | 
					#ifdef NO_BOOTLOADER
 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
 | 
				
			|||||||
@ -272,7 +272,7 @@ const struct Version* furi_hal_version_get_firmware_version(void) {
 | 
				
			|||||||
    return version_get();
 | 
					    return version_get();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const struct Version* furi_hal_version_get_boot_version(void) {
 | 
					const struct Version* furi_hal_version_get_bootloader_version(void) {
 | 
				
			||||||
#ifdef NO_BOOTLOADER
 | 
					#ifdef NO_BOOTLOADER
 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
 | 
				
			|||||||
@ -148,7 +148,7 @@ const uint8_t* furi_hal_version_get_ble_mac();
 | 
				
			|||||||
 *
 | 
					 *
 | 
				
			||||||
 * @return     Address of boot version structure.
 | 
					 * @return     Address of boot version structure.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const struct Version* furi_hal_version_get_boot_version();
 | 
					const struct Version* furi_hal_version_get_bootloader_version();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Get address of version structure of firmware.
 | 
					/** Get address of version structure of firmware.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,7 @@ static void flipper_print_version(const char* target, const Version* version) {
 | 
				
			|||||||
void flipper_init() {
 | 
					void flipper_init() {
 | 
				
			||||||
    const Version* version;
 | 
					    const Version* version;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    version = (const Version*)furi_hal_version_get_boot_version();
 | 
					    version = (const Version*)furi_hal_version_get_bootloader_version();
 | 
				
			||||||
    flipper_print_version("Bootloader", version);
 | 
					    flipper_print_version("Bootloader", version);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    version = (const Version*)furi_hal_version_get_firmware_version();
 | 
					    version = (const Version*)furi_hal_version_get_firmware_version();
 | 
				
			||||||
 | 
				
			|||||||
@ -85,6 +85,7 @@ bool furi_record_destroy(const char* name) {
 | 
				
			|||||||
    furi_assert(record_data);
 | 
					    furi_assert(record_data);
 | 
				
			||||||
    if(record_data->holders_count == 0) {
 | 
					    if(record_data->holders_count == 0) {
 | 
				
			||||||
        FuriRecordDataDict_erase(furi_record->records, name_str);
 | 
					        FuriRecordDataDict_erase(furi_record->records, name_str);
 | 
				
			||||||
 | 
					        furi_check(osOK == osEventFlagsDelete(record_data->flags));
 | 
				
			||||||
        ret = true;
 | 
					        ret = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
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