Merge branch 'dev' into release-candidate
This commit is contained in:
		
						commit
						f3603e3c04
					
				
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@ -1,2 +1 @@
 | 
			
		||||
* 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
 | 
			
		||||
            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'
 | 
			
		||||
        if: ${{ !github.event.pull_request.head.repo.fork }}
 | 
			
		||||
        uses: ./.github/actions/docker
 | 
			
		||||
@ -116,25 +142,16 @@ jobs:
 | 
			
		||||
                artifacts/flipper-z-${TARGET}-bootloader-${{steps.names.outputs.suffix}}.bin
 | 
			
		||||
              mv bootloader/.obj/${TARGET}/bootloader.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 \
 | 
			
		||||
                artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.dfu
 | 
			
		||||
              mv firmware/.obj/${TARGET}/firmware.bin \
 | 
			
		||||
                artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.bin
 | 
			
		||||
              mv firmware/.obj/${TARGET}/firmware.elf \
 | 
			
		||||
                artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.elf
 | 
			
		||||
            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)"
 | 
			
		||||
              mv firmware/.obj/${TARGET}/firmware.json \
 | 
			
		||||
                artifacts/flipper-z-${TARGET}-firmware-${{steps.names.outputs.suffix}}.json
 | 
			
		||||
            done
 | 
			
		||||
 | 
			
		||||
      - name: 'Full flash asssembly: bootloader as base'
 | 
			
		||||
 | 
			
		||||
@ -129,11 +129,11 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage*
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static DialogMessageButton boot_version_screen(DialogsApp* dialogs, DialogMessage* message) {
 | 
			
		||||
static DialogMessageButton bootloader_version_screen(DialogsApp* dialogs, DialogMessage* message) {
 | 
			
		||||
    DialogMessageButton result;
 | 
			
		||||
    string_t buffer;
 | 
			
		||||
    string_init(buffer);
 | 
			
		||||
    const Version* ver = furi_hal_version_get_boot_version();
 | 
			
		||||
    const Version* ver = furi_hal_version_get_bootloader_version();
 | 
			
		||||
 | 
			
		||||
    if(!ver) {
 | 
			
		||||
        string_cat_printf(buffer, "No info\n");
 | 
			
		||||
@ -167,7 +167,7 @@ const AboutDialogScreen about_screens[] = {
 | 
			
		||||
    icon2_screen,
 | 
			
		||||
    hw_version_screen,
 | 
			
		||||
    fw_version_screen,
 | 
			
		||||
    boot_version_screen};
 | 
			
		||||
    bootloader_version_screen};
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
    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) {
 | 
			
		||||
        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);
 | 
			
		||||
        // Open RPC session
 | 
			
		||||
        FURI_LOG_I(BT_SERVICE_TAG, "Open RPC connection");
 | 
			
		||||
        bt->rpc_session = rpc_open_session(bt->rpc);
 | 
			
		||||
        rpc_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback, bt);
 | 
			
		||||
        bt->rpc_session = rpc_session_open(bt->rpc);
 | 
			
		||||
        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(
 | 
			
		||||
            bt_on_data_received_callback, bt_on_data_sent_callback, bt);
 | 
			
		||||
        // Update battery level
 | 
			
		||||
@ -142,7 +143,7 @@ static void bt_on_gap_event_callback(BleEvent event, void* context) {
 | 
			
		||||
    } else if(event.type == BleEventTypeDisconnected) {
 | 
			
		||||
        FURI_LOG_I(BT_SERVICE_TAG, "Close RPC connection");
 | 
			
		||||
        if(bt->rpc_session) {
 | 
			
		||||
            rpc_close_session(bt->rpc_session);
 | 
			
		||||
            rpc_session_close(bt->rpc_session);
 | 
			
		||||
            bt->rpc_session = NULL;
 | 
			
		||||
        }
 | 
			
		||||
    } else if(event.type == BleEventTypeStartAdvertising) {
 | 
			
		||||
 | 
			
		||||
@ -263,7 +263,7 @@ static void cli_handle_autocomplete(Cli* cli) {
 | 
			
		||||
        cli->cursor_position = string_size(cli->line);
 | 
			
		||||
    }
 | 
			
		||||
    // Cleanup
 | 
			
		||||
    string_clean(common);
 | 
			
		||||
    string_clear(common);
 | 
			
		||||
    // Show prompt
 | 
			
		||||
    cli_prompt(cli);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -56,7 +56,7 @@ static const uint8_t enclave_signature_expected[ENCLAVE_SIGNATURE_KEY_SLOTS][ENC
 | 
			
		||||
 */
 | 
			
		||||
void cli_command_device_info(Cli* cli, string_t args, void* context) {
 | 
			
		||||
    // 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);
 | 
			
		||||
    // Model name
 | 
			
		||||
    printf("hardware_model          : %s\r\n", furi_hal_version_get_model_name());
 | 
			
		||||
@ -89,14 +89,14 @@ void cli_command_device_info(Cli* cli, string_t args, void* context) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Bootloader Version
 | 
			
		||||
    const Version* boot_version = furi_hal_version_get_boot_version();
 | 
			
		||||
    if(boot_version) {
 | 
			
		||||
        printf("boot_commit         : %s\r\n", version_get_githash(boot_version));
 | 
			
		||||
        printf("boot_branch         : %s\r\n", version_get_gitbranch(boot_version));
 | 
			
		||||
        printf("boot_branch_num     : %s\r\n", version_get_gitbranchnum(boot_version));
 | 
			
		||||
        printf("boot_version        : %s\r\n", version_get_version(boot_version));
 | 
			
		||||
        printf("boot_build_date     : %s\r\n", version_get_builddate(boot_version));
 | 
			
		||||
        printf("boot_target         : %d\r\n", version_get_target(boot_version));
 | 
			
		||||
    const Version* bootloader_version = furi_hal_version_get_bootloader_version();
 | 
			
		||||
    if(bootloader_version) {
 | 
			
		||||
        printf("bootloader_commit       : %s\r\n", version_get_githash(bootloader_version));
 | 
			
		||||
        printf("bootloader_branch       : %s\r\n", version_get_gitbranch(bootloader_version));
 | 
			
		||||
        printf("bootloader_branch_num   : %s\r\n", version_get_gitbranchnum(bootloader_version));
 | 
			
		||||
        printf("bootloader_version      : %s\r\n", version_get_version(bootloader_version));
 | 
			
		||||
        printf("bootloader_build_date   : %s\r\n", version_get_builddate(bootloader_version));
 | 
			
		||||
        printf("bootloader_target       : %d\r\n", version_get_target(bootloader_version));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Firmware version
 | 
			
		||||
 | 
			
		||||
@ -41,6 +41,7 @@ Desktop* desktop_alloc() {
 | 
			
		||||
    desktop->debug_view = desktop_debug_alloc();
 | 
			
		||||
    desktop->first_start_view = desktop_first_start_alloc();
 | 
			
		||||
    desktop->hw_mismatch_popup = popup_alloc();
 | 
			
		||||
    desktop->code_input = code_input_alloc();
 | 
			
		||||
 | 
			
		||||
    view_dispatcher_add_view(
 | 
			
		||||
        desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view));
 | 
			
		||||
@ -62,7 +63,8 @@ Desktop* desktop_alloc() {
 | 
			
		||||
        desktop->view_dispatcher,
 | 
			
		||||
        DesktopViewHwMismatch,
 | 
			
		||||
        popup_get_view(desktop->hw_mismatch_popup));
 | 
			
		||||
 | 
			
		||||
    view_dispatcher_add_view(
 | 
			
		||||
        desktop->view_dispatcher, DesktopViewPinSetup, code_input_get_view(desktop->code_input));
 | 
			
		||||
    // Lock icon
 | 
			
		||||
    desktop->lock_viewport = view_port_alloc();
 | 
			
		||||
    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, DesktopViewFirstStart);
 | 
			
		||||
    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewHwMismatch);
 | 
			
		||||
    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewPinSetup);
 | 
			
		||||
 | 
			
		||||
    view_dispatcher_free(desktop->view_dispatcher);
 | 
			
		||||
    scene_manager_free(desktop->scene_manager);
 | 
			
		||||
@ -92,6 +95,7 @@ void desktop_free(Desktop* desktop) {
 | 
			
		||||
    desktop_debug_free(desktop->debug_view);
 | 
			
		||||
    desktop_first_start_free(desktop->first_start_view);
 | 
			
		||||
    popup_free(desktop->hw_mismatch_popup);
 | 
			
		||||
    code_input_free(desktop->code_input);
 | 
			
		||||
 | 
			
		||||
    furi_record_close("gui");
 | 
			
		||||
    desktop->gui = NULL;
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@
 | 
			
		||||
#include <gui/gui.h>
 | 
			
		||||
#include <gui/view_dispatcher.h>
 | 
			
		||||
#include <gui/modules/popup.h>
 | 
			
		||||
#include <gui/modules/code_input.h>
 | 
			
		||||
#include <gui/scene_manager.h>
 | 
			
		||||
#include <assets_icons.h>
 | 
			
		||||
#include <storage/storage.h>
 | 
			
		||||
@ -29,6 +30,7 @@ typedef enum {
 | 
			
		||||
    DesktopViewDebug,
 | 
			
		||||
    DesktopViewFirstStart,
 | 
			
		||||
    DesktopViewHwMismatch,
 | 
			
		||||
    DesktopViewPinSetup,
 | 
			
		||||
    DesktopViewTotal,
 | 
			
		||||
} DesktopViewEnum;
 | 
			
		||||
 | 
			
		||||
@ -46,7 +48,10 @@ struct Desktop {
 | 
			
		||||
    DesktopLockMenuView* lock_menu;
 | 
			
		||||
    DesktopLockedView* locked_view;
 | 
			
		||||
    DesktopDebugView* debug_view;
 | 
			
		||||
    CodeInput* code_input;
 | 
			
		||||
 | 
			
		||||
    DesktopSettings settings;
 | 
			
		||||
    PinCode pincode_buffer;
 | 
			
		||||
 | 
			
		||||
    ViewPort* lock_viewport;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -3,11 +3,20 @@
 | 
			
		||||
#include <stdint.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 {
 | 
			
		||||
    uint8_t version;
 | 
			
		||||
    uint16_t favorite;
 | 
			
		||||
 | 
			
		||||
    PinCode pincode;
 | 
			
		||||
    bool locked;
 | 
			
		||||
} DesktopSettings;
 | 
			
		||||
 | 
			
		||||
bool desktop_settings_load(DesktopSettings* desktop_settings);
 | 
			
		||||
 | 
			
		||||
@ -33,10 +33,13 @@ DesktopSettingsApp* desktop_settings_app_alloc() {
 | 
			
		||||
 | 
			
		||||
    app->submenu = submenu_alloc();
 | 
			
		||||
    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(
 | 
			
		||||
        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);
 | 
			
		||||
    return app;
 | 
			
		||||
@ -45,9 +48,10 @@ DesktopSettingsApp* desktop_settings_app_alloc() {
 | 
			
		||||
void desktop_settings_app_free(DesktopSettingsApp* app) {
 | 
			
		||||
    furi_assert(app);
 | 
			
		||||
    // Variable item list
 | 
			
		||||
    view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMain);
 | 
			
		||||
    view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewFavorite);
 | 
			
		||||
    view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
 | 
			
		||||
    submenu_free(app->submenu);
 | 
			
		||||
    view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewPincodeInput);
 | 
			
		||||
    code_input_free(app->code_input);
 | 
			
		||||
    // View dispatcher
 | 
			
		||||
    view_dispatcher_free(app->view_dispatcher);
 | 
			
		||||
    scene_manager_free(app->scene_manager);
 | 
			
		||||
 | 
			
		||||
@ -6,20 +6,32 @@
 | 
			
		||||
#include <gui/view_dispatcher.h>
 | 
			
		||||
#include <gui/scene_manager.h>
 | 
			
		||||
#include <gui/modules/submenu.h>
 | 
			
		||||
#include <gui/modules/code_input.h>
 | 
			
		||||
 | 
			
		||||
#include "desktop_settings.h"
 | 
			
		||||
 | 
			
		||||
#include "scenes/desktop_settings_scene.h"
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    DesktopSettingsAppViewMain,
 | 
			
		||||
    DesktopSettingsAppViewFavorite,
 | 
			
		||||
    CodeEventsSetPin,
 | 
			
		||||
    CodeEventsChangePin,
 | 
			
		||||
    CodeEventsDisablePin,
 | 
			
		||||
} CodeEventsEnum;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    DesktopSettingsAppViewMenu,
 | 
			
		||||
    DesktopSettingsAppViewPincodeInput,
 | 
			
		||||
} DesktopSettingsAppView;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    DesktopSettings settings;
 | 
			
		||||
 | 
			
		||||
    Gui* gui;
 | 
			
		||||
    SceneManager* scene_manager;
 | 
			
		||||
    ViewDispatcher* view_dispatcher;
 | 
			
		||||
    Submenu* submenu;
 | 
			
		||||
    CodeInput* code_input;
 | 
			
		||||
 | 
			
		||||
    uint8_t menu_idx;
 | 
			
		||||
 | 
			
		||||
} DesktopSettingsApp;
 | 
			
		||||
 | 
			
		||||
@ -1,2 +1,4 @@
 | 
			
		||||
ADD_SCENE(desktop_settings, start, Start)
 | 
			
		||||
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_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) {
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
        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) {
 | 
			
		||||
@ -39,7 +39,11 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        switch(event.event) {
 | 
			
		||||
        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;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -4,3 +4,4 @@ ADD_SCENE(desktop, locked, Locked)
 | 
			
		||||
ADD_SCENE(desktop, debug, Debug)
 | 
			
		||||
ADD_SCENE(desktop, first_start, FirstStart)
 | 
			
		||||
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) {
 | 
			
		||||
    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_pin_set(desktop->lock_menu, desktop->settings.pincode.length > 0);
 | 
			
		||||
    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) {
 | 
			
		||||
        switch(event.event) {
 | 
			
		||||
        case DesktopLockMenuEventLock:
 | 
			
		||||
            scene_manager_set_scene_state(
 | 
			
		||||
                desktop->scene_manager, DesktopSceneLocked, DesktopLockedNoPin);
 | 
			
		||||
            scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
            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:
 | 
			
		||||
            scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
 | 
			
		||||
@ -15,12 +15,39 @@ void desktop_scene_locked_on_enter(void* context) {
 | 
			
		||||
    desktop_locked_update_hint_timeout(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);
 | 
			
		||||
    osTimerStart(locked_view->timer, 63);
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
    Desktop* desktop = (Desktop*)context;
 | 
			
		||||
 | 
			
		||||
@ -36,7 +63,17 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
        case DesktopLockedEventUpdate:
 | 
			
		||||
            desktop_locked_manage_redraw(desktop->locked_view);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
            break;
 | 
			
		||||
        case DesktopLockedEventInputReset:
 | 
			
		||||
            desktop->pincode_buffer.length = 0;
 | 
			
		||||
            break;
 | 
			
		||||
        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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,8 @@ void desktop_scene_main_on_enter(void* context) {
 | 
			
		||||
    desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop);
 | 
			
		||||
    view_port_enabled_set(desktop->lock_viewport, false);
 | 
			
		||||
 | 
			
		||||
    desktop_settings_load(&desktop->settings);
 | 
			
		||||
 | 
			
		||||
    if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain) ==
 | 
			
		||||
       DesktopMainEventUnlocked) {
 | 
			
		||||
        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");
 | 
			
		||||
        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();
 | 
			
		||||
 | 
			
		||||
        if(!ver) {
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,14 @@ void desktop_lock_menu_set_callback(
 | 
			
		||||
    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) {
 | 
			
		||||
    with_view_model(
 | 
			
		||||
        lock_menu->view, (DesktopLockMenuViewModel * model) {
 | 
			
		||||
@ -26,6 +34,10 @@ static void lock_menu_callback(void* context, uint8_t index) {
 | 
			
		||||
    switch(index) {
 | 
			
		||||
    case 0: // lock
 | 
			
		||||
        lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context);
 | 
			
		||||
        break;
 | 
			
		||||
    case 1: // lock
 | 
			
		||||
        lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context);
 | 
			
		||||
        break;
 | 
			
		||||
    default: // wip message
 | 
			
		||||
        with_view_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) {
 | 
			
		||||
    const char* Lockmenu_Items[3] = {"Lock", "Set PIN", "DUMB mode"};
 | 
			
		||||
    const char* Lockmenu_Items[3] = {"Lock", "Lock with PIN", "DUMB mode"};
 | 
			
		||||
 | 
			
		||||
    DesktopLockMenuViewModel* m = model;
 | 
			
		||||
    canvas_clear(canvas);
 | 
			
		||||
@ -47,13 +59,13 @@ void desktop_lock_menu_render(Canvas* canvas, void* model) {
 | 
			
		||||
    canvas_set_font(canvas, FontSecondary);
 | 
			
		||||
 | 
			
		||||
    for(uint8_t i = 0; i < 3; ++i) {
 | 
			
		||||
        canvas_draw_str_aligned(
 | 
			
		||||
            canvas,
 | 
			
		||||
            64,
 | 
			
		||||
            13 + (i * 17),
 | 
			
		||||
            AlignCenter,
 | 
			
		||||
            AlignCenter,
 | 
			
		||||
            (m->hint_timeout && m->idx == i && m->idx) ? "Not implemented" : Lockmenu_Items[i]);
 | 
			
		||||
        const char* str = Lockmenu_Items[i];
 | 
			
		||||
 | 
			
		||||
        if(i == 1 && !m->pin_set) str = "Set PIN";
 | 
			
		||||
        if(m->hint_timeout && m->idx == 2 && m->idx == i) str = "Not implemented";
 | 
			
		||||
 | 
			
		||||
        canvas_draw_str_aligned(canvas, 64, 13 + (i * 17), AlignCenter, AlignCenter, str);
 | 
			
		||||
 | 
			
		||||
        if(m->idx == i) elements_frame(canvas, 15, 5 + (i * 17), 98, 15);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@
 | 
			
		||||
typedef enum {
 | 
			
		||||
    DesktopLockMenuEventLock,
 | 
			
		||||
    DesktopLockMenuEventUnlock,
 | 
			
		||||
    DesktopLockMenuEventPinLock,
 | 
			
		||||
    DesktopLockMenuEventExit,
 | 
			
		||||
} DesktopLockMenuEvent;
 | 
			
		||||
 | 
			
		||||
@ -27,6 +28,7 @@ struct DesktopLockMenuView {
 | 
			
		||||
typedef struct {
 | 
			
		||||
    uint8_t idx;
 | 
			
		||||
    uint8_t hint_timeout;
 | 
			
		||||
    bool pin_set;
 | 
			
		||||
} DesktopLockMenuViewModel;
 | 
			
		||||
 | 
			
		||||
void desktop_lock_menu_set_callback(
 | 
			
		||||
@ -35,6 +37,7 @@ void desktop_lock_menu_set_callback(
 | 
			
		||||
    void* context);
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
DesktopLockMenuView* desktop_lock_menu_alloc();
 | 
			
		||||
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) {
 | 
			
		||||
    DesktopLockedViewModel* m = model;
 | 
			
		||||
    uint32_t now = osKernelGetTickCount();
 | 
			
		||||
@ -100,7 +108,7 @@ void desktop_locked_render(Canvas* canvas, void* model) {
 | 
			
		||||
            canvas_set_font(canvas, FontPrimary);
 | 
			
		||||
            elements_multiline_text_framed(canvas, 42, 30, "Locked");
 | 
			
		||||
 | 
			
		||||
        } else {
 | 
			
		||||
        } else if(!m->pin_lock) {
 | 
			
		||||
            canvas_set_font(canvas, FontSecondary);
 | 
			
		||||
            canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49);
 | 
			
		||||
            elements_multiline_text(canvas, 65, 20, "To unlock\npress:");
 | 
			
		||||
@ -116,18 +124,34 @@ View* desktop_locked_get_view(DesktopLockedView* locked_view) {
 | 
			
		||||
bool desktop_locked_input(InputEvent* event, void* context) {
 | 
			
		||||
    furi_assert(event);
 | 
			
		||||
    furi_assert(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(locked_with_pin) {
 | 
			
		||||
            press_time = osKernelGetTickCount();
 | 
			
		||||
 | 
			
		||||
            if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT * 3) {
 | 
			
		||||
                locked_view->lock_lastpress = press_time;
 | 
			
		||||
                locked_view->callback(DesktopLockedEventInputReset, locked_view->context);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            locked_view->callback(event->key, locked_view->context);
 | 
			
		||||
        } else {
 | 
			
		||||
            desktop_locked_update_hint_timeout(locked_view);
 | 
			
		||||
 | 
			
		||||
            if(event->key == InputKeyBack) {
 | 
			
		||||
            uint32_t press_time = osKernelGetTickCount();
 | 
			
		||||
                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 = 0;
 | 
			
		||||
            } else if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) {
 | 
			
		||||
                if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) {
 | 
			
		||||
                    locked_view->lock_lastpress = press_time;
 | 
			
		||||
                    locked_view->lock_count++;
 | 
			
		||||
                }
 | 
			
		||||
@ -138,6 +162,12 @@ bool desktop_locked_input(InputEvent* event, void* context) {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) {
 | 
			
		||||
            locked_view->lock_lastpress = press_time;
 | 
			
		||||
            locked_view->lock_count = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // All events consumed
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -15,10 +15,16 @@
 | 
			
		||||
#define DOOR_R_POS_MIN 60
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    DesktopLockedEventUnlock,
 | 
			
		||||
    DesktopLockedEventUpdate,
 | 
			
		||||
    DesktopLockedEventUnlock = 10U,
 | 
			
		||||
    DesktopLockedEventUpdate = 11U,
 | 
			
		||||
    DesktopLockedEventInputReset = 12U,
 | 
			
		||||
} DesktopLockedEvent;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    DesktopLockedWithPin,
 | 
			
		||||
    DesktopLockedNoPin,
 | 
			
		||||
} DesktopLockedSceneState;
 | 
			
		||||
 | 
			
		||||
typedef struct DesktopLockedView DesktopLockedView;
 | 
			
		||||
 | 
			
		||||
typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context);
 | 
			
		||||
@ -42,6 +48,7 @@ typedef struct {
 | 
			
		||||
    int8_t door_right_x;
 | 
			
		||||
    bool animation_seq_end;
 | 
			
		||||
 | 
			
		||||
    bool pin_lock;
 | 
			
		||||
} DesktopLockedViewModel;
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
DesktopLockedView* desktop_locked_alloc();
 | 
			
		||||
void desktop_locked_free(DesktopLockedView* locked_view);
 | 
			
		||||
void desktop_main_unlocked(DesktopMainView* main_view);
 | 
			
		||||
void desktop_main_reset_hint(DesktopMainView* main_view);
 | 
			
		||||
void desktop_locked_with_pin(DesktopLockedView* lock_menu, bool locked);
 | 
			
		||||
@ -67,6 +67,7 @@ bool desktop_main_input(InputEvent* event, void* context) {
 | 
			
		||||
    } else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
 | 
			
		||||
        main_view->callback(DesktopMainEventOpenFavorite, main_view->context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    desktop_main_reset_hint(main_view);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
@ -7,12 +7,12 @@
 | 
			
		||||
#include <furi.h>
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    DesktopMainEventOpenMenu,
 | 
			
		||||
    DesktopMainEventOpenLockMenu,
 | 
			
		||||
    DesktopMainEventOpenDebug,
 | 
			
		||||
    DesktopMainEventUnlocked,
 | 
			
		||||
    DesktopMainEventOpenArchive,
 | 
			
		||||
    DesktopMainEventOpenFavorite,
 | 
			
		||||
    DesktopMainEventOpenMenu,
 | 
			
		||||
    DesktopMainEventOpenDebug,
 | 
			
		||||
    DesktopMainEventUnlocked,
 | 
			
		||||
} DesktopMainEvent;
 | 
			
		||||
 | 
			
		||||
typedef struct DesktopMainView DesktopMainView;
 | 
			
		||||
@ -37,9 +37,8 @@ void desktop_main_set_callback(
 | 
			
		||||
    void* context);
 | 
			
		||||
 | 
			
		||||
View* desktop_main_get_view(DesktopMainView* main_view);
 | 
			
		||||
 | 
			
		||||
DesktopMainView* desktop_main_alloc();
 | 
			
		||||
 | 
			
		||||
void desktop_main_free(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 "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 {
 | 
			
		||||
    Gui* gui;
 | 
			
		||||
    ViewDispatcher* view_dispatcher;
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,6 @@
 | 
			
		||||
#include "../gpio_app_i.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 {
 | 
			
		||||
    GpioItemOtg,
 | 
			
		||||
    GpioItemTest,
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,6 @@
 | 
			
		||||
#include "../usb_uart_bridge.h"
 | 
			
		||||
#include "../gpio_app_i.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 {
 | 
			
		||||
    UsbUartLineIndexVcp,
 | 
			
		||||
@ -21,42 +10,7 @@ typedef enum {
 | 
			
		||||
    UsbUartLineIndexDisable,
 | 
			
		||||
} LineIndex;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    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 UsbUartConfig* cfg_set;
 | 
			
		||||
 | 
			
		||||
static const char* vcp_ch[] = {"0 (CLI)", "1"};
 | 
			
		||||
static const char* uart_ch[] = {"USART1", "LPUART1"};
 | 
			
		||||
@ -73,197 +27,14 @@ static const uint32_t baudrate_list[] = {
 | 
			
		||||
    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) {
 | 
			
		||||
    //GpioApp* app = context;
 | 
			
		||||
    bool consumed = false;
 | 
			
		||||
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == UsbUartLineIndexEnable) {
 | 
			
		||||
            usb_uart_enable();
 | 
			
		||||
        } else if(event.event == UsbUartLineIndexDisable) {
 | 
			
		||||
        if(event.event == GPIO_SCENE_USB_UART_CUSTOM_EVENT_ENABLE) {
 | 
			
		||||
            usb_uart_enable(cfg_set);
 | 
			
		||||
        } else if(event.event == GPIO_SCENE_USB_UART_CUSTOM_EVENT_DISABLE) {
 | 
			
		||||
            usb_uart_disable();
 | 
			
		||||
        }
 | 
			
		||||
        consumed = true;
 | 
			
		||||
@ -271,15 +42,13 @@ bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    return consumed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Scene callbacks */
 | 
			
		||||
 | 
			
		||||
static void line_vcp_cb(VariableItem* item) {
 | 
			
		||||
    //GpioApp* app = variable_item_get_context(item);
 | 
			
		||||
    uint8_t index = variable_item_get_current_value_index(item);
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
@ -288,34 +57,44 @@ static void line_port_cb(VariableItem* item) {
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
    //GpioApp* app = variable_item_get_context(item);
 | 
			
		||||
    uint8_t index = variable_item_get_current_value_index(item);
 | 
			
		||||
 | 
			
		||||
    char br_text[8];
 | 
			
		||||
 | 
			
		||||
    if(index > 0) {
 | 
			
		||||
        snprintf(usb_uart->br_text, 7, "%lu", baudrate_list[index - 1]);
 | 
			
		||||
        variable_item_set_current_value_text(item, usb_uart->br_text);
 | 
			
		||||
        usb_uart->cfg_set.baudrate = baudrate_list[index - 1];
 | 
			
		||||
        snprintf(br_text, 7, "%lu", baudrate_list[index - 1]);
 | 
			
		||||
        variable_item_set_current_value_text(item, br_text);
 | 
			
		||||
        cfg_set->baudrate = baudrate_list[index - 1];
 | 
			
		||||
    } else {
 | 
			
		||||
        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) {
 | 
			
		||||
    furi_assert(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) {
 | 
			
		||||
    GpioApp* app = context;
 | 
			
		||||
    VariableItemList* var_item_list = app->var_item_list;
 | 
			
		||||
 | 
			
		||||
    usb_uart = furi_alloc(sizeof(UsbUartParams));
 | 
			
		||||
    cfg_set = furi_alloc(sizeof(UsbUartConfig));
 | 
			
		||||
 | 
			
		||||
    VariableItem* item;
 | 
			
		||||
 | 
			
		||||
@ -348,5 +127,5 @@ void gpio_scene_usb_uart_on_exit(void* context) {
 | 
			
		||||
    GpioApp* app = context;
 | 
			
		||||
    usb_uart_disable();
 | 
			
		||||
    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 <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 = furi_alloc(sizeof(Canvas));
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
    // send init sequence to the display, display is in sleep mode after this
 | 
			
		||||
    canvas->orientation = CanvasOrientationHorizontal;
 | 
			
		||||
    // Initialize display
 | 
			
		||||
    u8g2_InitDisplay(&canvas->fb);
 | 
			
		||||
    // wake up display
 | 
			
		||||
    u8g2_ClearBuffer(&canvas->fb);
 | 
			
		||||
    // Wake up display
 | 
			
		||||
    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();
 | 
			
		||||
 | 
			
		||||
    return canvas;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -32,9 +42,12 @@ void canvas_free(Canvas* canvas) {
 | 
			
		||||
 | 
			
		||||
void canvas_reset(Canvas* canvas) {
 | 
			
		||||
    furi_assert(canvas);
 | 
			
		||||
 | 
			
		||||
    canvas_clear(canvas);
 | 
			
		||||
 | 
			
		||||
    canvas_set_color(canvas, ColorBlack);
 | 
			
		||||
    canvas_set_font(canvas, FontSecondary);
 | 
			
		||||
    canvas_set_font_direction(canvas, CanvasFontDirectionLeftToRight);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void canvas_commit(Canvas* canvas) {
 | 
			
		||||
@ -86,6 +99,12 @@ uint8_t canvas_current_font_height(Canvas* canvas) {
 | 
			
		||||
    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) {
 | 
			
		||||
    furi_assert(canvas);
 | 
			
		||||
    u8g2_ClearBuffer(&canvas->fb);
 | 
			
		||||
@ -96,6 +115,11 @@ void canvas_set_color(Canvas* canvas, Color 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) {
 | 
			
		||||
    canvas->fb.draw_color = !canvas->fb.draw_color;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,15 @@ typedef enum {
 | 
			
		||||
} Color;
 | 
			
		||||
 | 
			
		||||
/** 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 */
 | 
			
		||||
typedef enum {
 | 
			
		||||
@ -37,6 +45,22 @@ typedef enum {
 | 
			
		||||
    CanvasOrientationVertical,
 | 
			
		||||
} 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 */
 | 
			
		||||
typedef struct Canvas Canvas;
 | 
			
		||||
 | 
			
		||||
@ -64,6 +88,15 @@ uint8_t canvas_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
 | 
			
		||||
 *
 | 
			
		||||
 * @param      canvas  Canvas instance
 | 
			
		||||
@ -77,6 +110,14 @@ void canvas_clear(Canvas* canvas);
 | 
			
		||||
 */
 | 
			
		||||
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
 | 
			
		||||
 *
 | 
			
		||||
 * @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 <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(
 | 
			
		||||
    Canvas* canvas,
 | 
			
		||||
@ -352,3 +364,206 @@ void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) {
 | 
			
		||||
        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" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define ELEMENTS_MAX_LINES_NUM (7)
 | 
			
		||||
#define ELEMENTS_BOLD_MARKER '#'
 | 
			
		||||
#define ELEMENTS_MONO_MARKER '*'
 | 
			
		||||
#define ELEMENTS_INVERSED_MARKER '!'
 | 
			
		||||
 | 
			
		||||
/** Draw progress bar.
 | 
			
		||||
 * @param x - progress bar position on X axis
 | 
			
		||||
 * @param y - progress bar position on Y axis
 | 
			
		||||
 * @param width - progress bar width
 | 
			
		||||
 * @param progress - progress in unnamed metric
 | 
			
		||||
 * @param total - total amount in unnamed metric
 | 
			
		||||
 *
 | 
			
		||||
 * @param   canvas      Canvas instance
 | 
			
		||||
 * @param   x           progress bar position on X axis
 | 
			
		||||
 * @param   y           progress bar position on Y axis
 | 
			
		||||
 * @param   width       progress bar width
 | 
			
		||||
 * @param   progress    progress in unnamed metric
 | 
			
		||||
 * @param   total       total amount in unnamed metric
 | 
			
		||||
 */
 | 
			
		||||
void elements_progress_bar(
 | 
			
		||||
    Canvas* canvas,
 | 
			
		||||
@ -32,11 +39,13 @@ void elements_progress_bar(
 | 
			
		||||
    uint8_t total);
 | 
			
		||||
 | 
			
		||||
/** Draw scrollbar on canvas at specific position.
 | 
			
		||||
 * @param x - scrollbar position on X axis
 | 
			
		||||
 * @param y - scrollbar position on Y axis
 | 
			
		||||
 * @param height - scrollbar height
 | 
			
		||||
 * @param pos - current element 
 | 
			
		||||
 * @param total - total elements
 | 
			
		||||
 *
 | 
			
		||||
 * @param   canvas  Canvas instance
 | 
			
		||||
 * @param   x       scrollbar position on X axis
 | 
			
		||||
 * @param   y       scrollbar position on Y axis
 | 
			
		||||
 * @param   height  scrollbar height
 | 
			
		||||
 * @param   pos     current element
 | 
			
		||||
 * @param   total   total elements
 | 
			
		||||
 */
 | 
			
		||||
void elements_scrollbar_pos(
 | 
			
		||||
    Canvas* canvas,
 | 
			
		||||
@ -47,37 +56,49 @@ void elements_scrollbar_pos(
 | 
			
		||||
    uint16_t total);
 | 
			
		||||
 | 
			
		||||
/** Draw scrollbar on canvas.
 | 
			
		||||
 * width 3px, height equal to canvas height
 | 
			
		||||
 * @param pos - current element of total elements
 | 
			
		||||
 * @param total - total elements
 | 
			
		||||
 * @note    width 3px, height equal to canvas height
 | 
			
		||||
 *
 | 
			
		||||
 * @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);
 | 
			
		||||
 | 
			
		||||
/** 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);
 | 
			
		||||
 | 
			
		||||
/** 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);
 | 
			
		||||
 | 
			
		||||
/** 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);
 | 
			
		||||
 | 
			
		||||
/** 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);
 | 
			
		||||
 | 
			
		||||
/** Draw aligned multiline text
 | 
			
		||||
 * @param x, y - coordinates based on align param
 | 
			
		||||
 * @param horizontal, vertical - aligment of multiline text
 | 
			
		||||
 * @param text - string (possible multiline)
 | 
			
		||||
 *
 | 
			
		||||
 * @param   canvas                  Canvas instance
 | 
			
		||||
 * @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(
 | 
			
		||||
    Canvas* canvas,
 | 
			
		||||
@ -88,20 +109,26 @@ void elements_multiline_text_aligned(
 | 
			
		||||
    const char* 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);
 | 
			
		||||
 | 
			
		||||
/** 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);
 | 
			
		||||
 | 
			
		||||
/** 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(
 | 
			
		||||
    Canvas* canvas,
 | 
			
		||||
@ -111,8 +138,10 @@ void elements_slightly_rounded_frame(
 | 
			
		||||
    uint8_t height);
 | 
			
		||||
 | 
			
		||||
/** 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(
 | 
			
		||||
    Canvas* canvas,
 | 
			
		||||
@ -122,19 +151,47 @@ void elements_slightly_rounded_box(
 | 
			
		||||
    uint8_t height);
 | 
			
		||||
 | 
			
		||||
/** Draw bubble frame for text
 | 
			
		||||
 * @param x - left x coordinates
 | 
			
		||||
 * @param y - top y coordinate
 | 
			
		||||
 * @param width - bubble width
 | 
			
		||||
 * @param height - bubble height
 | 
			
		||||
 *
 | 
			
		||||
 * @param   canvas  Canvas instance
 | 
			
		||||
 * @param   x       left x coordinates
 | 
			
		||||
 * @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);
 | 
			
		||||
 | 
			
		||||
/** 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);
 | 
			
		||||
 | 
			
		||||
/** 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
 | 
			
		||||
}
 | 
			
		||||
#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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(
 | 
			
		||||
    Widget* widget,
 | 
			
		||||
    GuiButtonType button_type,
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,30 @@ void widget_add_string_element(
 | 
			
		||||
    Font font,
 | 
			
		||||
    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
 | 
			
		||||
 *
 | 
			
		||||
 * @param      widget       Widget instance
 | 
			
		||||
 | 
			
		||||
@ -52,6 +52,16 @@ WidgetElement* widget_element_string_create(
 | 
			
		||||
    Font font,
 | 
			
		||||
    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 */
 | 
			
		||||
WidgetElement* widget_element_button_create(
 | 
			
		||||
    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
 | 
			
		||||
    char delete_str[64];
 | 
			
		||||
    snprintf(delete_str, sizeof(delete_str), "Delete %s", nfc->dev.dev_name);
 | 
			
		||||
    widget_add_string_element(nfc->widget, 64, 6, AlignCenter, AlignTop, FontPrimary, delete_str);
 | 
			
		||||
    snprintf(delete_str, sizeof(delete_str), "\e#Delete %s\e#", nfc->dev.dev_name);
 | 
			
		||||
    widget_add_text_box_element(nfc->widget, 0, 0, 128, 24, AlignCenter, AlignCenter, delete_str);
 | 
			
		||||
    widget_add_button_element(
 | 
			
		||||
        nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_delete_widget_callback, nfc);
 | 
			
		||||
    widget_add_button_element(
 | 
			
		||||
 | 
			
		||||
@ -35,8 +35,8 @@ void nfc_scene_device_info_on_enter(void* context) {
 | 
			
		||||
    Nfc* nfc = context;
 | 
			
		||||
 | 
			
		||||
    // Setup Custom Widget view
 | 
			
		||||
    widget_add_string_element(
 | 
			
		||||
        nfc->widget, 64, 6, AlignCenter, AlignTop, FontSecondary, nfc->dev.dev_name);
 | 
			
		||||
    widget_add_text_box_element(
 | 
			
		||||
        nfc->widget, 0, 0, 128, 24, AlignCenter, AlignCenter, nfc->dev.dev_name);
 | 
			
		||||
    widget_add_button_element(
 | 
			
		||||
        nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_device_info_widget_callback, nfc);
 | 
			
		||||
    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);
 | 
			
		||||
    if(c == 'y' || c == 'Y') {
 | 
			
		||||
        printf("Data will be wiped after reboot.\r\n");
 | 
			
		||||
        furi_hal_boot_set_flags(FuriHalBootFlagFactoryReset);
 | 
			
		||||
        furi_hal_bootloader_set_flags(FuriHalBootloaderFlagFactoryReset);
 | 
			
		||||
        power_reboot(PowerBootModeNormal);
 | 
			
		||||
    } else {
 | 
			
		||||
        printf("Safe choice.\r\n");
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
#include "power_i.h"
 | 
			
		||||
#include <furi.h>
 | 
			
		||||
#include "furi-hal-power.h"
 | 
			
		||||
#include "furi-hal-boot.h"
 | 
			
		||||
#include "furi-hal-bootloader.h"
 | 
			
		||||
 | 
			
		||||
void power_off(Power* power) {
 | 
			
		||||
    furi_hal_power_off();
 | 
			
		||||
@ -14,9 +14,9 @@ void power_off(Power* power) {
 | 
			
		||||
 | 
			
		||||
void power_reboot(PowerBootMode mode) {
 | 
			
		||||
    if(mode == PowerBootModeNormal) {
 | 
			
		||||
        furi_hal_boot_set_mode(FuriHalBootModeNormal);
 | 
			
		||||
        furi_hal_bootloader_set_mode(FuriHalBootloaderModeNormal);
 | 
			
		||||
    } else if(mode == PowerBootModeDfu) {
 | 
			
		||||
        furi_hal_boot_set_mode(FuriHalBootModeDFU);
 | 
			
		||||
        furi_hal_bootloader_set_mode(FuriHalBootloaderModeDFU);
 | 
			
		||||
    }
 | 
			
		||||
    furi_hal_power_reset();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,22 +1,20 @@
 | 
			
		||||
#include "cmsis_os.h"
 | 
			
		||||
#include "cmsis_os2.h"
 | 
			
		||||
#include "flipper.pb.h"
 | 
			
		||||
#include "furi-hal-delay.h"
 | 
			
		||||
#include "furi/check.h"
 | 
			
		||||
#include "furi/log.h"
 | 
			
		||||
#include <m-string.h>
 | 
			
		||||
#include "pb.h"
 | 
			
		||||
#include "pb_decode.h"
 | 
			
		||||
#include "pb_encode.h"
 | 
			
		||||
#include "portmacro.h"
 | 
			
		||||
#include "status.pb.h"
 | 
			
		||||
#include "storage.pb.h"
 | 
			
		||||
#include "rpc_i.h"
 | 
			
		||||
#include <pb.h>
 | 
			
		||||
#include <pb_decode.h>
 | 
			
		||||
#include <pb_encode.h>
 | 
			
		||||
#include <status.pb.h>
 | 
			
		||||
#include <storage.pb.h>
 | 
			
		||||
#include <flipper.pb.h>
 | 
			
		||||
#include <cmsis_os.h>
 | 
			
		||||
#include <cmsis_os2.h>
 | 
			
		||||
#include <portmacro.h>
 | 
			
		||||
#include <furi.h>
 | 
			
		||||
#include <cli/cli.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <furi.h>
 | 
			
		||||
#include <stream_buffer.h>
 | 
			
		||||
#include <m-string.h>
 | 
			
		||||
#include <m-dict.h>
 | 
			
		||||
#include "rpc_i.h"
 | 
			
		||||
 | 
			
		||||
#define RPC_TAG "RPC"
 | 
			
		||||
 | 
			
		||||
@ -51,10 +49,11 @@ static RpcSystemCallbacks rpc_systems[] = {
 | 
			
		||||
 | 
			
		||||
struct RpcSession {
 | 
			
		||||
    RpcSendBytesCallback send_bytes_callback;
 | 
			
		||||
    void* send_bytes_context;
 | 
			
		||||
    osMutexId_t send_bytes_mutex;
 | 
			
		||||
    RpcSessionClosedCallback closed_callback;
 | 
			
		||||
    void* context;
 | 
			
		||||
    osMutexId_t callbacks_mutex;
 | 
			
		||||
    Rpc* rpc;
 | 
			
		||||
    bool terminate_session;
 | 
			
		||||
    bool terminate;
 | 
			
		||||
    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 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(
 | 
			
		||||
    string_t str,
 | 
			
		||||
    const char* prefix,
 | 
			
		||||
@ -105,6 +118,31 @@ static size_t rpc_sprintf_msg_file(
 | 
			
		||||
    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) {
 | 
			
		||||
    string_t str;
 | 
			
		||||
    string_init(str);
 | 
			
		||||
@ -120,6 +158,9 @@ void rpc_print_message(const PB_Main* message) {
 | 
			
		||||
        /* not implemented yet */
 | 
			
		||||
        string_cat_printf(str, "\tNOT_IMPLEMENTED (%d) {\r\n", message->which_content);
 | 
			
		||||
        break;
 | 
			
		||||
    case PB_Main_stop_session_tag:
 | 
			
		||||
        string_cat_printf(str, "\tstop_session {\r\n");
 | 
			
		||||
        break;
 | 
			
		||||
    case PB_Main_app_start_tag: {
 | 
			
		||||
        string_cat_printf(str, "\tapp_start {\r\n");
 | 
			
		||||
        const char* name = message->content.app_start.name;
 | 
			
		||||
@ -242,7 +283,7 @@ static Rpc* rpc_alloc(void) {
 | 
			
		||||
    return rpc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RpcSession* rpc_open_session(Rpc* rpc) {
 | 
			
		||||
RpcSession* rpc_session_open(Rpc* rpc) {
 | 
			
		||||
    furi_assert(rpc);
 | 
			
		||||
    bool result = false;
 | 
			
		||||
    furi_check(osMutexAcquire(rpc->busy_mutex, osWaitForever) == osOK);
 | 
			
		||||
@ -256,41 +297,94 @@ RpcSession* rpc_open_session(Rpc* rpc) {
 | 
			
		||||
 | 
			
		||||
    if(result) {
 | 
			
		||||
        RpcSession* session = &rpc->session;
 | 
			
		||||
        session->send_bytes_mutex = osMutexNew(NULL);
 | 
			
		||||
        session->callbacks_mutex = osMutexNew(NULL);
 | 
			
		||||
        session->rpc = rpc;
 | 
			
		||||
        session->terminate_session = false;
 | 
			
		||||
        session->terminate = false;
 | 
			
		||||
        xStreamBufferReset(rpc->stream);
 | 
			
		||||
 | 
			
		||||
        session->system_contexts = furi_alloc(COUNT_OF(rpc_systems) * sizeof(void*));
 | 
			
		||||
        for(int i = 0; i < COUNT_OF(rpc_systems); ++i) {
 | 
			
		||||
            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");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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->rpc);
 | 
			
		||||
    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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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->rpc);
 | 
			
		||||
    furi_assert(session->rpc->busy);
 | 
			
		||||
 | 
			
		||||
    osMutexAcquire(session->send_bytes_mutex, osWaitForever);
 | 
			
		||||
    session->send_bytes_callback = callback;
 | 
			
		||||
    session->send_bytes_context = context;
 | 
			
		||||
    osMutexRelease(session->send_bytes_mutex);
 | 
			
		||||
    osMutexAcquire(session->callbacks_mutex, osWaitForever);
 | 
			
		||||
    session->context = context;
 | 
			
		||||
    osMutexRelease(session->callbacks_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
 | 
			
		||||
    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);
 | 
			
		||||
    Rpc* rpc = session->rpc;
 | 
			
		||||
    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;
 | 
			
		||||
    size_t bytes_received = 0;
 | 
			
		||||
 | 
			
		||||
    furi_assert(istream->bytes_left);
 | 
			
		||||
 | 
			
		||||
    while(1) {
 | 
			
		||||
        bytes_received +=
 | 
			
		||||
            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);
 | 
			
		||||
            if(flags & RPC_EVENT_DISCONNECT) {
 | 
			
		||||
                if(xStreamBufferIsEmpty(rpc->stream)) {
 | 
			
		||||
                    rpc->session.terminate_session = true;
 | 
			
		||||
                    rpc->session.terminate = true;
 | 
			
		||||
                    istream->bytes_left = 0;
 | 
			
		||||
                    bytes_received = 0;
 | 
			
		||||
                    break;
 | 
			
		||||
                } else {
 | 
			
		||||
                    /* 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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(main_message);
 | 
			
		||||
    furi_assert(message);
 | 
			
		||||
    RpcSession* session = &rpc->session;
 | 
			
		||||
    pb_ostream_t ostream = PB_OSTREAM_SIZING;
 | 
			
		||||
 | 
			
		||||
#if DEBUG_PRINT
 | 
			
		||||
    FURI_LOG_I(RPC_TAG, "OUTPUT:");
 | 
			
		||||
    rpc_print_message(main_message);
 | 
			
		||||
    rpc_print_message(message);
 | 
			
		||||
#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);
 | 
			
		||||
 | 
			
		||||
    uint8_t* buffer = furi_alloc(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
 | 
			
		||||
        string_t str;
 | 
			
		||||
        string_init(str);
 | 
			
		||||
        string_reserve(str, 100 + ostream.bytes_written * 5);
 | 
			
		||||
    rpc_print_data("OUTPUT", buffer, ostream.bytes_written);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
        string_cat_printf(str, "\r\nREPONSE DEC(%d): {", ostream.bytes_written);
 | 
			
		||||
        for(int i = 0; i < ostream.bytes_written; ++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 + 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);
 | 
			
		||||
    osMutexAcquire(session->callbacks_mutex, osWaitForever);
 | 
			
		||||
    if(session->send_bytes_callback) {
 | 
			
		||||
            session->send_bytes_callback(
 | 
			
		||||
                session->send_bytes_context, buffer, ostream.bytes_written);
 | 
			
		||||
        }
 | 
			
		||||
        osMutexRelease(session->send_bytes_mutex);
 | 
			
		||||
        session->send_bytes_callback(session->context, buffer, ostream.bytes_written);
 | 
			
		||||
    }
 | 
			
		||||
    osMutexRelease(session->callbacks_mutex);
 | 
			
		||||
 | 
			
		||||
    free(buffer);
 | 
			
		||||
    pb_release(&PB_Main_msg, message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
    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) {
 | 
			
		||||
        pb_istream_t istream = {
 | 
			
		||||
            .callback = rpc_pb_stream_read,
 | 
			
		||||
            .state = rpc,
 | 
			
		||||
            .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)) {
 | 
			
		||||
@ -417,35 +503,25 @@ int32_t rpc_srv(void* p) {
 | 
			
		||||
 | 
			
		||||
            if(handler && handler->message_handler) {
 | 
			
		||||
                handler->message_handler(rpc->decoded_message, handler->context);
 | 
			
		||||
            } else if(!handler) {
 | 
			
		||||
            } else if(!handler && !rpc->session.terminate) {
 | 
			
		||||
                FURI_LOG_E(
 | 
			
		||||
                    RPC_TAG,
 | 
			
		||||
                    "Unhandled message, tag: %d\r\n",
 | 
			
		||||
                    rpc->decoded_message->which_content);
 | 
			
		||||
                    RPC_TAG, "Unhandled message, tag: %d", rpc->decoded_message->which_content);
 | 
			
		||||
            }
 | 
			
		||||
            pb_release(&PB_Main_msg, rpc->decoded_message);
 | 
			
		||||
        } else {
 | 
			
		||||
            pb_release(&PB_Main_msg, rpc->decoded_message);
 | 
			
		||||
            RpcSession* session = &rpc->session;
 | 
			
		||||
            if(session->terminate_session) {
 | 
			
		||||
                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));
 | 
			
		||||
            if(!rpc->session.terminate) {
 | 
			
		||||
                FURI_LOG_E(RPC_TAG, "Decode failed, error: \'%.128s\'", 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;
 | 
			
		||||
}
 | 
			
		||||
@ -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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 = {
 | 
			
		||||
        .command_id = command_id,
 | 
			
		||||
        .command_status = status,
 | 
			
		||||
        .has_next = false,
 | 
			
		||||
        .which_content = PB_Main_empty_tag,
 | 
			
		||||
    };
 | 
			
		||||
    rpc_encode_and_send(rpc, &message);
 | 
			
		||||
    rpc_send_and_release(rpc, &message);
 | 
			
		||||
    pb_release(&PB_Main_msg, &message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,79 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include "cmsis_os.h"
 | 
			
		||||
 | 
			
		||||
/** Rpc interface. Used for opening session only. */
 | 
			
		||||
typedef struct Rpc Rpc;
 | 
			
		||||
/** Rpc session interface */
 | 
			
		||||
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);
 | 
			
		||||
/** 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);
 | 
			
		||||
void rpc_close_session(RpcSession* session);
 | 
			
		||||
/* WARN: can't call RPC API within RpcSendBytesCallback */
 | 
			
		||||
void rpc_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback, void* context);
 | 
			
		||||
size_t
 | 
			
		||||
    rpc_feed_bytes(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout);
 | 
			
		||||
/** Open RPC session
 | 
			
		||||
 *
 | 
			
		||||
 * USAGE:
 | 
			
		||||
 * 1) rpc_session_open();
 | 
			
		||||
 * 2) rpc_session_set_context();
 | 
			
		||||
 * 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");
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
@ -56,7 +56,8 @@ void rpc_system_app_lock_status_process(const PB_Main* request, void* context) {
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
#include "rpc.h"
 | 
			
		||||
#include "pb.h"
 | 
			
		||||
#include "pb_decode.h"
 | 
			
		||||
#include "pb_encode.h"
 | 
			
		||||
#include "flipper.pb.h"
 | 
			
		||||
#include <pb.h>
 | 
			
		||||
#include <pb_decode.h>
 | 
			
		||||
#include <pb_encode.h>
 | 
			
		||||
#include <flipper.pb.h>
 | 
			
		||||
#include <cli/cli.h>
 | 
			
		||||
 | 
			
		||||
typedef void* (*RpcSystemAlloc)(Rpc*);
 | 
			
		||||
typedef void (*RpcSystemFree)(void*);
 | 
			
		||||
@ -15,8 +16,8 @@ typedef struct {
 | 
			
		||||
    void* context;
 | 
			
		||||
} RpcHandler;
 | 
			
		||||
 | 
			
		||||
void rpc_encode_and_send(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(Rpc* rpc, PB_Main* main_message);
 | 
			
		||||
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_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_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.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) {
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@ static void rpc_system_storage_reset_state(RpcStorageSystem* rpc_storage, bool s
 | 
			
		||||
 | 
			
		||||
    if(rpc_storage->state != RpcStorageStateIdle) {
 | 
			
		||||
        if(send_error) {
 | 
			
		||||
            rpc_encode_and_send_empty(
 | 
			
		||||
            rpc_send_and_release_empty(
 | 
			
		||||
                rpc_storage->rpc,
 | 
			
		||||
                rpc_storage->current_command_id,
 | 
			
		||||
                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));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
    furi_assert(request);
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
@ -104,6 +129,11 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
 | 
			
		||||
    RpcStorageSystem* rpc_storage = context;
 | 
			
		||||
    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");
 | 
			
		||||
    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)) {
 | 
			
		||||
                list->file_count = i;
 | 
			
		||||
                response.has_next = true;
 | 
			
		||||
                rpc_encode_and_send(rpc_storage->rpc, &response);
 | 
			
		||||
                pb_release(&PB_Main_msg, &response);
 | 
			
		||||
                rpc_send_and_release(rpc_storage->rpc, &response);
 | 
			
		||||
                i = 0;
 | 
			
		||||
            }
 | 
			
		||||
            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;
 | 
			
		||||
    rpc_encode_and_send(rpc_storage->rpc, &response);
 | 
			
		||||
    pb_release(&PB_Main_msg, &response);
 | 
			
		||||
    rpc_send_and_release(rpc_storage->rpc, &response);
 | 
			
		||||
 | 
			
		||||
    storage_dir_close(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 */
 | 
			
		||||
    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;
 | 
			
		||||
    Storage* fs_api = furi_record_open("storage");
 | 
			
		||||
    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)) {
 | 
			
		||||
        size_t size_left = storage_file_size(file);
 | 
			
		||||
        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)));
 | 
			
		||||
        do {
 | 
			
		||||
            uint8_t* buffer = response->content.storage_read_response.file.data->bytes;
 | 
			
		||||
            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) {
 | 
			
		||||
                response->has_next = (size_left > 0);
 | 
			
		||||
                rpc_encode_and_send(rpc_storage->rpc, response);
 | 
			
		||||
                // no pb_release(...);
 | 
			
		||||
                rpc_send_and_release(rpc_storage->rpc, response);
 | 
			
		||||
            }
 | 
			
		||||
        } while((size_left != 0) && 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));
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        rpc_encode_and_send_empty(
 | 
			
		||||
        rpc_send_and_release_empty(
 | 
			
		||||
            rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pb_release(&PB_Main_msg, response);
 | 
			
		||||
    free(response);
 | 
			
		||||
    storage_file_close(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);
 | 
			
		||||
 | 
			
		||||
        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_system_storage_reset_state(rpc_storage, false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(!result) {
 | 
			
		||||
        rpc_encode_and_send_empty(
 | 
			
		||||
        rpc_send_and_release_empty(
 | 
			
		||||
            rpc_storage->rpc,
 | 
			
		||||
            rpc_storage->current_command_id,
 | 
			
		||||
            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) {
 | 
			
		||||
    furi_assert(request);
 | 
			
		||||
    furi_assert(request->which_content == PB_Main_storage_delete_request_tag);
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
    RpcStorageSystem* rpc_storage = context;
 | 
			
		||||
    PB_CommandStatus status;
 | 
			
		||||
    PB_CommandStatus status = PB_CommandStatus_ERROR;
 | 
			
		||||
    rpc_system_storage_reset_state(rpc_storage, true);
 | 
			
		||||
 | 
			
		||||
    Storage* fs_api = furi_record_open("storage");
 | 
			
		||||
    char* path = request->content.storage_mkdir_request.path;
 | 
			
		||||
    if(path) {
 | 
			
		||||
        FS_Error error = storage_common_remove(fs_api, path);
 | 
			
		||||
        status = rpc_system_storage_get_error(error);
 | 
			
		||||
    } else {
 | 
			
		||||
 | 
			
		||||
    char* path = request->content.storage_delete_request.path;
 | 
			
		||||
    if(!path) {
 | 
			
		||||
        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;
 | 
			
		||||
            }
 | 
			
		||||
    rpc_encode_and_send_empty(rpc_storage->rpc, request->command_id, status);
 | 
			
		||||
        } else if(error_remove == FSE_NOT_EXIST) {
 | 
			
		||||
            status = PB_CommandStatus_OK;
 | 
			
		||||
        } else {
 | 
			
		||||
            status = rpc_system_storage_get_error(error_remove);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
@ -295,7 +355,7 @@ static void rpc_system_storage_mkdir_process(const PB_Main* request, void* conte
 | 
			
		||||
    } else {
 | 
			
		||||
        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) {
 | 
			
		||||
@ -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;
 | 
			
		||||
    if(!filename) {
 | 
			
		||||
        rpc_encode_and_send_empty(
 | 
			
		||||
        rpc_send_and_release_empty(
 | 
			
		||||
            rpc_storage->rpc, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
@ -349,9 +409,9 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont
 | 
			
		||||
        free(hash);
 | 
			
		||||
        free(data);
 | 
			
		||||
        storage_file_close(file);
 | 
			
		||||
        rpc_encode_and_send(rpc_storage->rpc, &response);
 | 
			
		||||
        rpc_send_and_release(rpc_storage->rpc, &response);
 | 
			
		||||
    } else {
 | 
			
		||||
        rpc_encode_and_send_empty(
 | 
			
		||||
        rpc_send_and_release_empty(
 | 
			
		||||
            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-i.h"
 | 
			
		||||
#include "storage-message.h"
 | 
			
		||||
 | 
			
		||||
#define MAX_NAME_LENGTH 256
 | 
			
		||||
 | 
			
		||||
#define S_API_PROLOGUE                                      \
 | 
			
		||||
    osSemaphoreId_t semaphore = osSemaphoreNew(1, 0, NULL); \
 | 
			
		||||
    furi_check(semaphore != NULL);
 | 
			
		||||
@ -382,6 +386,67 @@ void storage_file_free(File* 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) {
 | 
			
		||||
    FS_Error result;
 | 
			
		||||
    result = storage_common_remove(storage, path);
 | 
			
		||||
 | 
			
		||||
@ -240,6 +240,14 @@ FS_Error storage_sd_status(Storage* api);
 | 
			
		||||
 */
 | 
			
		||||
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
 | 
			
		||||
 * @param storage 
 | 
			
		||||
 | 
			
		||||
@ -163,15 +163,15 @@ static LFSData* storage_int_lfs_data_alloc() {
 | 
			
		||||
 | 
			
		||||
static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) {
 | 
			
		||||
    int err;
 | 
			
		||||
    FuriHalBootFlag boot_flags = furi_hal_boot_get_flags();
 | 
			
		||||
    FuriHalBootloaderFlag bootloader_flags = furi_hal_bootloader_get_flags();
 | 
			
		||||
    lfs_t* lfs = &lfs_data->lfs;
 | 
			
		||||
 | 
			
		||||
    if(boot_flags & FuriHalBootFlagFactoryReset) {
 | 
			
		||||
    if(bootloader_flags & FuriHalBootloaderFlagFactoryReset) {
 | 
			
		||||
        // Factory reset
 | 
			
		||||
        err = lfs_format(lfs, &lfs_data->config);
 | 
			
		||||
        if(err == 0) {
 | 
			
		||||
            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);
 | 
			
		||||
            if(err == 0) {
 | 
			
		||||
                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;
 | 
			
		||||
@ -17,3 +17,6 @@ ADD_SCENE(subghz, test_carrier, TestCarrier)
 | 
			
		||||
ADD_SCENE(subghz, test_packet, TestPacket)
 | 
			
		||||
ADD_SCENE(subghz, set_type, SetType)
 | 
			
		||||
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"
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SubGhzSceneDeleteInfoCustomEventDelete,
 | 
			
		||||
} SubGhzSceneDeleteInfoCustomEvent;
 | 
			
		||||
#include "../helpers/subghz_custom_event.h"
 | 
			
		||||
 | 
			
		||||
void subghz_scene_delete_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, SubGhzSceneDeleteInfoCustomEventDelete);
 | 
			
		||||
        view_dispatcher_send_custom_event(subghz->view_dispatcher, SubghzCustomEventSceneDelete);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -53,7 +49,7 @@ void subghz_scene_delete_on_enter(void* context) {
 | 
			
		||||
bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubGhzSceneDeleteInfoCustomEventDelete) {
 | 
			
		||||
        if(event.event == SubghzCustomEventSceneDelete) {
 | 
			
		||||
            memcpy(subghz->file_name_tmp, subghz->file_name, strlen(subghz->file_name));
 | 
			
		||||
            if(subghz_delete_file(subghz)) {
 | 
			
		||||
                scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess);
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
 | 
			
		||||
#define SCENE_DELETE_SUCCESS_CUSTOM_EVENT (0UL)
 | 
			
		||||
#include "../helpers/subghz_custom_event.h"
 | 
			
		||||
 | 
			
		||||
void subghz_scene_delete_success_popup_callback(void* 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) {
 | 
			
		||||
@ -25,7 +25,7 @@ bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
 | 
			
		||||
    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(
 | 
			
		||||
                subghz->scene_manager, SubGhzSceneStart);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
#include "../subghz_i.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);
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    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) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubghzFrequencyAnalyzerEventOnlyRx) {
 | 
			
		||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    //SubGhz* subghz = context;
 | 
			
		||||
    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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_scene_receiver_callback(SubghzReceverEvent event, void* context) {
 | 
			
		||||
void subghz_scene_receiver_callback(SubghzCustomEvent event, void* context) {
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    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) {
 | 
			
		||||
        subghz_rx_end(subghz);
 | 
			
		||||
    };
 | 
			
		||||
    if((subghz->txrx->txrx_state == SubGhzTxRxStateIdle) ||
 | 
			
		||||
    if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) ||
 | 
			
		||||
       (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
 | 
			
		||||
        subghz_begin(subghz, subghz->txrx->preset);
 | 
			
		||||
        subghz_rx(subghz, subghz->txrx->frequency);
 | 
			
		||||
@ -102,8 +102,9 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        switch(event.event) {
 | 
			
		||||
        case SubghzReceverEventBack:
 | 
			
		||||
        case SubghzCustomEventViewReceverBack:
 | 
			
		||||
            // Stop CC1101 Rx
 | 
			
		||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
			
		||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
			
		||||
                subghz_rx_end(subghz);
 | 
			
		||||
                subghz_sleep(subghz);
 | 
			
		||||
@ -118,12 +119,12 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
                subghz->scene_manager, SubGhzSceneStart);
 | 
			
		||||
            return true;
 | 
			
		||||
            break;
 | 
			
		||||
        case SubghzReceverEventOK:
 | 
			
		||||
        case SubghzCustomEventViewReceverOK:
 | 
			
		||||
            subghz->txrx->idx_menu_chosen = subghz_receiver_get_idx_menu(subghz->subghz_receiver);
 | 
			
		||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo);
 | 
			
		||||
            return true;
 | 
			
		||||
            break;
 | 
			
		||||
        case SubghzReceverEventConfig:
 | 
			
		||||
        case SubghzCustomEventViewReceverConfig:
 | 
			
		||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
			
		||||
            subghz->txrx->idx_menu_chosen = subghz_receiver_get_idx_menu(subghz->subghz_receiver);
 | 
			
		||||
            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) {
 | 
			
		||||
        variable_item_set_current_value_text(item, subghz_frequencies_text[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_frequencies_text[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 {
 | 
			
		||||
        variable_item_set_current_value_text(
 | 
			
		||||
            (VariableItem*)scene_manager_get_scene_state(
 | 
			
		||||
                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];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    VariableItem* item;
 | 
			
		||||
@ -122,6 +126,8 @@ void subghz_scene_receiver_config_on_enter(void* context) {
 | 
			
		||||
    variable_item_set_current_value_index(item, value_index);
 | 
			
		||||
    variable_item_set_current_value_text(item, subghz_frequencies_text[value_index]);
 | 
			
		||||
 | 
			
		||||
    if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
 | 
			
		||||
       SubghzCustomEventManagerSet) {
 | 
			
		||||
        item = variable_item_list_add(
 | 
			
		||||
            subghz->variable_item_list,
 | 
			
		||||
            "Hopping:",
 | 
			
		||||
@ -132,6 +138,7 @@ void subghz_scene_receiver_config_on_enter(void* context) {
 | 
			
		||||
            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(
 | 
			
		||||
        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) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    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"
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SubGhzSceneReceiverInfoCustomEventTxStart,
 | 
			
		||||
    SubGhzSceneReceiverInfoCustomEventTxStop,
 | 
			
		||||
    SubGhzSceneReceiverInfoCustomEventSave,
 | 
			
		||||
} SubGhzSceneReceiverInfoCustomEvent;
 | 
			
		||||
#include "../helpers/subghz_custom_event.h"
 | 
			
		||||
 | 
			
		||||
void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) {
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
@ -12,13 +7,13 @@ void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, v
 | 
			
		||||
 | 
			
		||||
    if((result == GuiButtonTypeCenter) && (type == InputTypePress)) {
 | 
			
		||||
        view_dispatcher_send_custom_event(
 | 
			
		||||
            subghz->view_dispatcher, SubGhzSceneReceiverInfoCustomEventTxStart);
 | 
			
		||||
            subghz->view_dispatcher, SubghzCustomEventSceneReceiverInfoTxStart);
 | 
			
		||||
    } else if((result == GuiButtonTypeCenter) && (type == InputTypeRelease)) {
 | 
			
		||||
        view_dispatcher_send_custom_event(
 | 
			
		||||
            subghz->view_dispatcher, SubGhzSceneReceiverInfoCustomEventTxStop);
 | 
			
		||||
            subghz->view_dispatcher, SubghzCustomEventSceneReceiverInfoTxStop);
 | 
			
		||||
    } else if((result == GuiButtonTypeRight) && (type == InputTypeShort)) {
 | 
			
		||||
        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) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubGhzSceneReceiverInfoCustomEventTxStart) {
 | 
			
		||||
        if(event.event == SubghzCustomEventSceneReceiverInfoTxStart) {
 | 
			
		||||
            //CC1101 Stop RX -> Start TX
 | 
			
		||||
            if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
 | 
			
		||||
                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)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) {
 | 
			
		||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE ||
 | 
			
		||||
               subghz->txrx->txrx_state == SubGhzTxRxStateSleep) {
 | 
			
		||||
                if(!subghz_tx_start(subghz)) {
 | 
			
		||||
                    scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
 | 
			
		||||
                } else {
 | 
			
		||||
@ -120,13 +116,13 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        } else if(event.event == SubGhzSceneReceiverInfoCustomEventTxStop) {
 | 
			
		||||
        } else if(event.event == SubghzCustomEventSceneReceiverInfoTxStop) {
 | 
			
		||||
            //CC1101 Stop Tx -> Start RX
 | 
			
		||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
			
		||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
 | 
			
		||||
                subghz_tx_stop(subghz);
 | 
			
		||||
            }
 | 
			
		||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) {
 | 
			
		||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) {
 | 
			
		||||
                subghz_begin(subghz, subghz->txrx->preset);
 | 
			
		||||
                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;
 | 
			
		||||
            return true;
 | 
			
		||||
        } else if(event.event == SubGhzSceneReceiverInfoCustomEventSave) {
 | 
			
		||||
        } else if(event.event == SubghzCustomEventSceneReceiverInfoSave) {
 | 
			
		||||
            //CC1101 Stop RX -> Save
 | 
			
		||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
			
		||||
            if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) {
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,11 @@
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
#include <lib/toolbox/random_name.h>
 | 
			
		||||
#include "file-worker.h"
 | 
			
		||||
 | 
			
		||||
#define SCENE_SAVE_NAME_CUSTOM_EVENT (0UL)
 | 
			
		||||
#include "../helpers/subghz_custom_event.h"
 | 
			
		||||
 | 
			
		||||
void subghz_scene_save_name_text_input_callback(void* 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) {
 | 
			
		||||
@ -21,6 +20,10 @@ void subghz_scene_save_name_on_enter(void* context) {
 | 
			
		||||
        dev_name_empty = true;
 | 
			
		||||
    } else {
 | 
			
		||||
        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");
 | 
			
		||||
@ -38,12 +41,14 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SCENE_SAVE_NAME_CUSTOM_EVENT) {
 | 
			
		||||
            if(strcmp(subghz->file_name, "") &&
 | 
			
		||||
               subghz_save_protocol_to_file(subghz, subghz->file_name)) {
 | 
			
		||||
        if(event.event == SubghzCustomEventSceneSaveName) {
 | 
			
		||||
            if(strcmp(subghz->file_name, "")) {
 | 
			
		||||
                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);
 | 
			
		||||
                scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess);
 | 
			
		||||
                return true;
 | 
			
		||||
@ -62,4 +67,6 @@ void subghz_scene_save_name_on_exit(void* context) {
 | 
			
		||||
 | 
			
		||||
    // Clear view
 | 
			
		||||
    text_input_clean(subghz->text_input);
 | 
			
		||||
    scene_manager_set_scene_state(
 | 
			
		||||
        subghz->scene_manager, SubGhzSceneReadRAWMenu, SubghzCustomEventManagerNoSet);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,9 @@
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
 | 
			
		||||
#define SCENE_SAVE_SUCCESS_CUSTOM_EVENT (0UL)
 | 
			
		||||
#include "../helpers/subghz_custom_event.h"
 | 
			
		||||
 | 
			
		||||
void subghz_scene_save_success_popup_callback(void* 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) {
 | 
			
		||||
@ -24,7 +23,7 @@ void subghz_scene_save_success_on_enter(void* context) {
 | 
			
		||||
bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    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(
 | 
			
		||||
                   subghz->scene_manager, SubGhzSceneReceiver)) {
 | 
			
		||||
                scene_manager_search_and_switch_to_previous_scene(
 | 
			
		||||
 | 
			
		||||
@ -19,12 +19,14 @@ void subghz_scene_saved_menu_on_enter(void* context) {
 | 
			
		||||
        SubmenuIndexEmulate,
 | 
			
		||||
        subghz_scene_saved_menu_submenu_callback,
 | 
			
		||||
        subghz);
 | 
			
		||||
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
        subghz->submenu,
 | 
			
		||||
        "Edit name",
 | 
			
		||||
        SubmenuIndexEdit,
 | 
			
		||||
        subghz_scene_saved_menu_submenu_callback,
 | 
			
		||||
        subghz);
 | 
			
		||||
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
        subghz->submenu,
 | 
			
		||||
        "Delete",
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,9 @@
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
 | 
			
		||||
#define SCENE_NO_MAN_CUSTOM_EVENT (11UL)
 | 
			
		||||
#include "../helpers/subghz_custom_event.h"
 | 
			
		||||
 | 
			
		||||
void subghz_scene_show_error_popup_callback(void* 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) {
 | 
			
		||||
@ -24,7 +23,7 @@ void subghz_scene_show_error_on_enter(void* context) {
 | 
			
		||||
bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SCENE_NO_MAN_CUSTOM_EVENT) {
 | 
			
		||||
        if(event.event == SubghzCustomEventSceneShowError) {
 | 
			
		||||
            scene_manager_search_and_switch_to_previous_scene(
 | 
			
		||||
                subghz->scene_manager, SubGhzSceneStart);
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,9 @@
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
 | 
			
		||||
#define SCENE_NO_MAN_CUSTOM_EVENT (11UL)
 | 
			
		||||
#include "../helpers/subghz_custom_event.h"
 | 
			
		||||
 | 
			
		||||
void subghz_scene_show_only_rx_popup_callback(void* 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) {
 | 
			
		||||
@ -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) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SCENE_NO_MAN_CUSTOM_EVENT) {
 | 
			
		||||
        if(event.event == SubghzCustomEventSceneShowOnlyRX) {
 | 
			
		||||
            scene_manager_previous_scene(subghz->scene_manager);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,12 @@
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
 | 
			
		||||
enum SubmenuIndex {
 | 
			
		||||
    SubmenuIndexRead,
 | 
			
		||||
    SubmenuIndexRead = 10,
 | 
			
		||||
    SubmenuIndexSaved,
 | 
			
		||||
    SubmenuIndexTest,
 | 
			
		||||
    SubmenuIndexAddManualy,
 | 
			
		||||
    SubmenuIndexFrequencyAnalyzer,
 | 
			
		||||
    SubmenuIndexReadRAW,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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(
 | 
			
		||||
        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(
 | 
			
		||||
        subghz->submenu, "Saved", SubmenuIndexSaved, subghz_scene_start_submenu_callback, subghz);
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
@ -47,7 +54,12 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
 | 
			
		||||
    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(
 | 
			
		||||
                subghz->scene_manager, SubGhzSceneStart, SubmenuIndexRead);
 | 
			
		||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver);
 | 
			
		||||
 | 
			
		||||
@ -2,13 +2,13 @@
 | 
			
		||||
#include "../views/subghz_transmitter.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);
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
    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,
 | 
			
		||||
            show_button);
 | 
			
		||||
        string_clear(key_str);
 | 
			
		||||
    } else {
 | 
			
		||||
        string_set(subghz->error_str, "Protocol not found");
 | 
			
		||||
        scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_scene_transmitter_on_enter(void* 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->subghz_transmitter, subghz_scene_transmitter_callback, subghz);
 | 
			
		||||
    subghz_scene_transmitter_update_data_show(subghz);
 | 
			
		||||
 | 
			
		||||
    subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
			
		||||
    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) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubghzTransmitterEventSendStart) {
 | 
			
		||||
        if(event.event == SubghzCustomEventViewTransmitterSendStart) {
 | 
			
		||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
			
		||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
			
		||||
                subghz_rx_end(subghz);
 | 
			
		||||
            }
 | 
			
		||||
            if((subghz->txrx->txrx_state == SubGhzTxRxStateIdle) ||
 | 
			
		||||
            if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) ||
 | 
			
		||||
               (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
 | 
			
		||||
                if(!subghz_tx_start(subghz)) {
 | 
			
		||||
                    scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
 | 
			
		||||
@ -84,18 +89,21 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        } else if(event.event == SubghzTransmitterEventSendStop) {
 | 
			
		||||
        } else if(event.event == SubghzCustomEventViewTransmitterSendStop) {
 | 
			
		||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
			
		||||
            if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
 | 
			
		||||
                subghz_tx_stop(subghz);
 | 
			
		||||
                subghz_sleep(subghz);
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        } else if(event.event == SubghzTransmitterEventBack) {
 | 
			
		||||
        } else if(event.event == SubghzCustomEventViewTransmitterBack) {
 | 
			
		||||
            subghz->state_notifications = NOTIFICATION_IDLE_STATE;
 | 
			
		||||
            scene_manager_search_and_switch_to_previous_scene(
 | 
			
		||||
                subghz->scene_manager, SubGhzSceneStart);
 | 
			
		||||
            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) {
 | 
			
		||||
        if(subghz->state_notifications == NOTIFICATION_TX_STATE) {
 | 
			
		||||
 | 
			
		||||
@ -140,6 +140,13 @@ SubGhz* subghz_alloc() {
 | 
			
		||||
        SubGhzViewFrequencyAnalyzer,
 | 
			
		||||
        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
 | 
			
		||||
    subghz->subghz_test_carrier = subghz_test_carrier_alloc();
 | 
			
		||||
    view_dispatcher_add_view(
 | 
			
		||||
@ -167,6 +174,7 @@ SubGhz* subghz_alloc() {
 | 
			
		||||
    subghz->txrx->preset = FuriHalSubGhzPresetOok650Async;
 | 
			
		||||
    subghz->txrx->txrx_state = SubGhzTxRxStateSleep;
 | 
			
		||||
    subghz->txrx->hopper_state = SubGhzHopperStateOFF;
 | 
			
		||||
    subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
 | 
			
		||||
    subghz->txrx->history = subghz_history_alloc();
 | 
			
		||||
    subghz->txrx->worker = subghz_worker_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_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);
 | 
			
		||||
 | 
			
		||||
@ -226,6 +235,10 @@ void subghz_free(SubGhz* subghz) {
 | 
			
		||||
    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewFrequencyAnalyzer);
 | 
			
		||||
    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
 | 
			
		||||
    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewMenu);
 | 
			
		||||
    submenu_free(subghz->submenu);
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ void subghz_begin(SubGhz* subghz, FuriHalSubGhzPreset preset) {
 | 
			
		||||
    furi_hal_subghz_idle();
 | 
			
		||||
    furi_hal_subghz_load_preset(preset);
 | 
			
		||||
    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) {
 | 
			
		||||
@ -59,7 +59,7 @@ void subghz_idle(SubGhz* subghz) {
 | 
			
		||||
    furi_assert(subghz);
 | 
			
		||||
    furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep);
 | 
			
		||||
    furi_hal_subghz_idle();
 | 
			
		||||
    subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
 | 
			
		||||
    subghz->txrx->txrx_state = SubGhzTxRxStateIDLE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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_idle();
 | 
			
		||||
    subghz->txrx->txrx_state = SubGhzTxRxStateIdle;
 | 
			
		||||
    subghz->txrx->txrx_state = SubGhzTxRxStateIDLE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_sleep(SubGhz* subghz) {
 | 
			
		||||
@ -191,7 +191,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        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;
 | 
			
		||||
        }
 | 
			
		||||
        loaded = true;
 | 
			
		||||
@ -208,6 +208,30 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) {
 | 
			
		||||
    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) {
 | 
			
		||||
    furi_assert(subghz);
 | 
			
		||||
    furi_assert(subghz->txrx->protocol_result);
 | 
			
		||||
@ -334,8 +358,10 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) {
 | 
			
		||||
        if(subghz->txrx->protocol_result == NULL) {
 | 
			
		||||
            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;
 | 
			
		||||
        }
 | 
			
		||||
        res = true;
 | 
			
		||||
@ -354,6 +380,28 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) {
 | 
			
		||||
    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) {
 | 
			
		||||
    furi_assert(subghz);
 | 
			
		||||
 | 
			
		||||
@ -442,7 +490,7 @@ void subghz_hopper_update(SubGhz* subghz) {
 | 
			
		||||
    if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
 | 
			
		||||
        subghz_rx_end(subghz);
 | 
			
		||||
    };
 | 
			
		||||
    if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) {
 | 
			
		||||
    if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) {
 | 
			
		||||
        subghz_parser_reset(subghz->txrx->parser);
 | 
			
		||||
        subghz->txrx->frequency = subghz_hopper_frequencies[subghz->txrx->hopper_idx_frequency];
 | 
			
		||||
        subghz_rx(subghz, subghz->txrx->frequency);
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@
 | 
			
		||||
#include "views/subghz_receiver.h"
 | 
			
		||||
#include "views/subghz_transmitter.h"
 | 
			
		||||
#include "views/subghz_frequency_analyzer.h"
 | 
			
		||||
#include "views/subghz_read_raw.h"
 | 
			
		||||
 | 
			
		||||
#include "views/subghz_test_static.h"
 | 
			
		||||
#include "views/subghz_test_carrier.h"
 | 
			
		||||
@ -46,7 +47,7 @@ extern const uint32_t subghz_frequencies_433_92;
 | 
			
		||||
 | 
			
		||||
/** SubGhzTxRx state */
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SubGhzTxRxStateIdle,
 | 
			
		||||
    SubGhzTxRxStateIDLE,
 | 
			
		||||
    SubGhzTxRxStateRx,
 | 
			
		||||
    SubGhzTxRxStateTx,
 | 
			
		||||
    SubGhzTxRxStateSleep,
 | 
			
		||||
@ -60,6 +61,15 @@ typedef enum {
 | 
			
		||||
    SubGhzHopperStateRSSITimeOut,
 | 
			
		||||
} SubGhzHopperState;
 | 
			
		||||
 | 
			
		||||
/** SubGhzRxKeyState state */
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SubGhzRxKeyStateIDLE,
 | 
			
		||||
    SubGhzRxKeyStateNoSave,
 | 
			
		||||
    SubGhzRxKeyStateNeedSave,
 | 
			
		||||
    SubGhzRxKeyStateAddKey,
 | 
			
		||||
    SubGhzRxKeyStateExit,
 | 
			
		||||
} SubGhzRxKeyState;
 | 
			
		||||
 | 
			
		||||
struct SubGhzTxRx {
 | 
			
		||||
    SubGhzWorker* worker;
 | 
			
		||||
    SubGhzParser* parser;
 | 
			
		||||
@ -70,10 +80,10 @@ struct SubGhzTxRx {
 | 
			
		||||
    SubGhzHistory* history;
 | 
			
		||||
    uint16_t idx_menu_chosen;
 | 
			
		||||
    SubGhzTxRxState txrx_state;
 | 
			
		||||
    //bool hopper_runing;
 | 
			
		||||
    SubGhzHopperState hopper_state;
 | 
			
		||||
    uint8_t hopper_timeout;
 | 
			
		||||
    uint8_t hopper_idx_frequency;
 | 
			
		||||
    SubGhzRxKeyState rx_key_state;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef struct SubGhzTxRx SubGhzTxRx;
 | 
			
		||||
@ -100,6 +110,7 @@ struct SubGhz {
 | 
			
		||||
    VariableItemList* variable_item_list;
 | 
			
		||||
 | 
			
		||||
    SubghzFrequencyAnalyzer* subghz_frequency_analyzer;
 | 
			
		||||
    SubghzReadRAW* subghz_read_raw;
 | 
			
		||||
    SubghzTestStatic* subghz_test_static;
 | 
			
		||||
    SubghzTestCarrier* subghz_test_carrier;
 | 
			
		||||
    SubghzTestPacket* subghz_test_packet;
 | 
			
		||||
@ -116,6 +127,7 @@ typedef enum {
 | 
			
		||||
    SubGhzViewTransmitter,
 | 
			
		||||
    SubGhzViewVariableItemList,
 | 
			
		||||
    SubGhzViewFrequencyAnalyzer,
 | 
			
		||||
    SubGhzViewReadRAW,
 | 
			
		||||
    SubGhzViewStatic,
 | 
			
		||||
    SubGhzViewTestCarrier,
 | 
			
		||||
    SubGhzViewTestPacket,
 | 
			
		||||
@ -128,8 +140,10 @@ void subghz_sleep(SubGhz* subghz);
 | 
			
		||||
bool subghz_tx_start(SubGhz* subghz);
 | 
			
		||||
void subghz_tx_stop(SubGhz* subghz);
 | 
			
		||||
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_load_protocol_from_file(SubGhz* subghz);
 | 
			
		||||
bool subghz_rename_file(SubGhz* subghz);
 | 
			
		||||
bool subghz_delete_file(SubGhz* subghz);
 | 
			
		||||
void subghz_file_name_clear(SubGhz* subghz);
 | 
			
		||||
uint32_t subghz_random_serial(void);
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,11 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <gui/view.h>
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SubghzFrequencyAnalyzerEventOnlyRx,
 | 
			
		||||
} SubghzFrequencyAnalyzerEvent;
 | 
			
		||||
#include "../helpers/subghz_custom_event.h"
 | 
			
		||||
 | 
			
		||||
typedef struct SubghzFrequencyAnalyzer SubghzFrequencyAnalyzer;
 | 
			
		||||
 | 
			
		||||
typedef void (*SubghzFrequencyAnalyzerCallback)(SubghzFrequencyAnalyzerEvent event, void* context);
 | 
			
		||||
typedef void (*SubghzFrequencyAnalyzerCallback)(SubghzCustomEvent event, void* context);
 | 
			
		||||
 | 
			
		||||
void subghz_frequency_analyzer_set_callback(
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
    if(event->key == InputKeyBack && event->type == InputTypeShort) {
 | 
			
		||||
        subghz_receiver->callback(SubghzReceverEventBack, subghz_receiver->context);
 | 
			
		||||
        subghz_receiver->callback(SubghzCustomEventViewReceverBack, subghz_receiver->context);
 | 
			
		||||
    } else if(
 | 
			
		||||
        event->key == InputKeyUp &&
 | 
			
		||||
        (event->type == InputTypeShort || event->type == InputTypeRepeat)) {
 | 
			
		||||
@ -199,12 +199,13 @@ bool subghz_receiver_input(InputEvent* event, void* context) {
 | 
			
		||||
                return true;
 | 
			
		||||
            });
 | 
			
		||||
    } 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) {
 | 
			
		||||
        with_view_model(
 | 
			
		||||
            subghz_receiver->view, (SubghzReceiverModel * model) {
 | 
			
		||||
                if(model->history_item != 0) {
 | 
			
		||||
                    subghz_receiver->callback(SubghzReceverEventOK, subghz_receiver->context);
 | 
			
		||||
                    subghz_receiver->callback(
 | 
			
		||||
                        SubghzCustomEventViewReceverOK, subghz_receiver->context);
 | 
			
		||||
                }
 | 
			
		||||
                return false;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,11 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <gui/view.h>
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SubghzReceverEventOK,
 | 
			
		||||
    SubghzReceverEventConfig,
 | 
			
		||||
    SubghzReceverEventBack,
 | 
			
		||||
} SubghzReceverEvent;
 | 
			
		||||
#include "../helpers/subghz_custom_event.h"
 | 
			
		||||
 | 
			
		||||
typedef struct SubghzReceiver SubghzReceiver;
 | 
			
		||||
 | 
			
		||||
typedef void (*SubghzReceiverCallback)(SubghzReceverEvent event, void* context);
 | 
			
		||||
typedef void (*SubghzReceiverCallback)(SubghzCustomEvent event, void* context);
 | 
			
		||||
 | 
			
		||||
void subghz_receiver_set_callback(
 | 
			
		||||
    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) {
 | 
			
		||||
        subghz_transmitter->callback(SubghzTransmitterEventSendStart, subghz_transmitter->context);
 | 
			
		||||
        subghz_transmitter->callback(
 | 
			
		||||
            SubghzCustomEventViewTransmitterSendStart, subghz_transmitter->context);
 | 
			
		||||
        return true;
 | 
			
		||||
    } 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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,11 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <gui/view.h>
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SubghzTransmitterEventSendStart,
 | 
			
		||||
    SubghzTransmitterEventSendStop,
 | 
			
		||||
    SubghzTransmitterEventBack,
 | 
			
		||||
    SubghzTransmitterEventNoMan,
 | 
			
		||||
} SubghzTransmitterEvent;
 | 
			
		||||
#include "../helpers/subghz_custom_event.h"
 | 
			
		||||
 | 
			
		||||
typedef struct SubghzTransmitter SubghzTransmitter;
 | 
			
		||||
 | 
			
		||||
typedef void (*SubghzTransmitterCallback)(SubghzTransmitterEvent event, void* context);
 | 
			
		||||
typedef void (*SubghzTransmitterCallback)(SubghzCustomEvent event, void* context);
 | 
			
		||||
 | 
			
		||||
void subghz_transmitter_set_callback(
 | 
			
		||||
    SubghzTransmitter* subghz_transmitter,
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,10 @@ void test_furi_create_open() {
 | 
			
		||||
    // 2. Open it
 | 
			
		||||
    void* record = furi_record_open("test/holding");
 | 
			
		||||
    mu_assert_pointers_eq(record, &test_data);
 | 
			
		||||
 | 
			
		||||
    // 3. Close it
 | 
			
		||||
    furi_record_close("test/holding");
 | 
			
		||||
 | 
			
		||||
    // 4. Clean up
 | 
			
		||||
    furi_record_destroy("test/holding");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -325,7 +325,6 @@ MU_TEST_SUITE(test_irda_decoder_encoder) {
 | 
			
		||||
 | 
			
		||||
int run_minunit_test_irda_decoder_encoder() {
 | 
			
		||||
    MU_RUN_SUITE(test_irda_decoder_encoder);
 | 
			
		||||
    MU_REPORT();
 | 
			
		||||
 | 
			
		||||
    return MU_EXIT_CODE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,9 @@ extern "C" {
 | 
			
		||||
__attribute__((unused)) static void (*minunit_setup)(void) = NULL;
 | 
			
		||||
__attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
 | 
			
		||||
void minunit_print_progress(void);
 | 
			
		||||
void minunit_print_fail(const char* error);
 | 
			
		||||
 | 
			
		||||
/*  Definitions */
 | 
			
		||||
#define MU_TEST(method_name) static void method_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++;                                           \
 | 
			
		||||
        if(minunit_status) {                                     \
 | 
			
		||||
            minunit_fail++;                                      \
 | 
			
		||||
            printf("F");                                         \
 | 
			
		||||
            printf("\n%s\n", minunit_last_message);              \
 | 
			
		||||
            minunit_print_fail(minunit_last_message);            \
 | 
			
		||||
        } fflush(stdout);                                        \
 | 
			
		||||
        if(minunit_teardown)(*minunit_teardown)();)
 | 
			
		||||
 | 
			
		||||
@ -142,7 +144,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                #test);                    \
 | 
			
		||||
            minunit_status = 1;            \
 | 
			
		||||
            return;                        \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_fail(message)                          \
 | 
			
		||||
    MU__SAFE_BLOCK(minunit_assert++; snprintf(    \
 | 
			
		||||
@ -169,7 +171,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                message);                  \
 | 
			
		||||
            minunit_status = 1;            \
 | 
			
		||||
            return;                        \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_assert_int_eq(expected, result)                                                  \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                         \
 | 
			
		||||
@ -187,7 +189,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                minunit_tmp_r);                                                             \
 | 
			
		||||
            minunit_status = 1;                                                             \
 | 
			
		||||
            return;                                                                         \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_assert_int_not_eq(expected, result)                                              \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                         \
 | 
			
		||||
@ -204,7 +206,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                minunit_tmp_e);                                                             \
 | 
			
		||||
            minunit_status = 1;                                                             \
 | 
			
		||||
            return;                                                                         \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_assert_int_greater_than(val, result)                                        \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                    \
 | 
			
		||||
@ -222,7 +224,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                minunit_tmp_e);                                                        \
 | 
			
		||||
            minunit_status = 1;                                                        \
 | 
			
		||||
            return;                                                                    \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_assert_int_less_than(val, result)                                           \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                    \
 | 
			
		||||
@ -240,7 +242,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                minunit_tmp_e);                                                        \
 | 
			
		||||
            minunit_status = 1;                                                        \
 | 
			
		||||
            return;                                                                    \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_assert_int_between(expected_lower, expected_upper, result)              \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                \
 | 
			
		||||
@ -261,7 +263,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                minunit_tmp_m);                                                    \
 | 
			
		||||
            minunit_status = 1;                                                    \
 | 
			
		||||
            return;                                                                \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_assert_int_in(expected, array_length, result)                                 \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                      \
 | 
			
		||||
@ -288,7 +290,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                minunit_tmp_r);                                                          \
 | 
			
		||||
            minunit_status = 1;                                                          \
 | 
			
		||||
            return;                                                                      \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_assert_double_eq(expected, result)                                                     \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                               \
 | 
			
		||||
@ -309,7 +311,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                minunit_tmp_r);                                                                   \
 | 
			
		||||
            minunit_status = 1;                                                                   \
 | 
			
		||||
            return;                                                                               \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_assert_double_greater_than(val, result)                                           \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                          \
 | 
			
		||||
@ -327,7 +329,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                minunit_tmp_e);                                                              \
 | 
			
		||||
            minunit_status = 1;                                                              \
 | 
			
		||||
            return;                                                                          \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_assert_double_less_than(val, result)                                              \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                          \
 | 
			
		||||
@ -345,7 +347,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                minunit_tmp_e);                                                              \
 | 
			
		||||
            minunit_status = 1;                                                              \
 | 
			
		||||
            return;                                                                          \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_assert_double_between(expected_lower, expected_upper, result)                    \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                         \
 | 
			
		||||
@ -366,7 +368,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                minunit_tmp_m);                                                             \
 | 
			
		||||
            minunit_status = 1;                                                             \
 | 
			
		||||
            return;                                                                         \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_assert_string_eq(expected, result)                                         \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                   \
 | 
			
		||||
@ -386,11 +388,11 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
                minunit_tmp_r);                                                       \
 | 
			
		||||
            minunit_status = 1;                                                       \
 | 
			
		||||
            return;                                                                   \
 | 
			
		||||
        } else { printf("."); })
 | 
			
		||||
        } else { minunit_print_progress(); })
 | 
			
		||||
 | 
			
		||||
#define mu_assert_null(result)                                                    \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                               \
 | 
			
		||||
        minunit_assert++; if(result == NULL) { printf("."); } else { \
 | 
			
		||||
        minunit_assert++; if(result == NULL) { minunit_print_progress(); } else { \
 | 
			
		||||
            snprintf(                                                             \
 | 
			
		||||
                minunit_last_message,                                             \
 | 
			
		||||
                MINUNIT_MESSAGE_LEN,                                              \
 | 
			
		||||
@ -404,7 +406,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
 | 
			
		||||
#define mu_assert_not_null(result)                                                \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                               \
 | 
			
		||||
        minunit_assert++; if(result != NULL) { printf("."); } else { \
 | 
			
		||||
        minunit_assert++; if(result != NULL) { minunit_print_progress(); } else { \
 | 
			
		||||
            snprintf(                                                             \
 | 
			
		||||
                minunit_last_message,                                             \
 | 
			
		||||
                MINUNIT_MESSAGE_LEN,                                              \
 | 
			
		||||
@ -418,7 +420,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
 | 
			
		||||
#define mu_assert_pointers_eq(pointer1, pointer2)                                                  \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                                \
 | 
			
		||||
        minunit_assert++; if(pointer1 == pointer2) { printf("."); } else {                         \
 | 
			
		||||
        minunit_assert++; if(pointer1 == pointer2) { minunit_print_progress(); } else {            \
 | 
			
		||||
            snprintf(                                                                              \
 | 
			
		||||
                minunit_last_message,                                                              \
 | 
			
		||||
                MINUNIT_MESSAGE_LEN,                                                               \
 | 
			
		||||
@ -432,7 +434,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
 | 
			
		||||
 | 
			
		||||
#define mu_assert_pointers_not_eq(pointer1, pointer2)                                              \
 | 
			
		||||
    MU__SAFE_BLOCK(                                                                                \
 | 
			
		||||
        minunit_assert++; if(pointer1 != pointer2) { printf("."); } else {                         \
 | 
			
		||||
        minunit_assert++; if(pointer1 != pointer2) { minunit_print_progress(); } else {            \
 | 
			
		||||
            snprintf(                                                                              \
 | 
			
		||||
                minunit_last_message,                                                              \
 | 
			
		||||
                MINUNIT_MESSAGE_LEN,                                                               \
 | 
			
		||||
 | 
			
		||||
@ -62,7 +62,6 @@ MU_TEST_SUITE(test_suite) {
 | 
			
		||||
 | 
			
		||||
int run_minunit() {
 | 
			
		||||
    MU_RUN_SUITE(test_suite);
 | 
			
		||||
    MU_REPORT();
 | 
			
		||||
 | 
			
		||||
    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_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(!session);
 | 
			
		||||
    furi_assert(!output_stream);
 | 
			
		||||
 | 
			
		||||
    rpc = furi_record_open("rpc");
 | 
			
		||||
    for(int i = 0; !session && (i < 10000); ++i) {
 | 
			
		||||
        session = rpc_open_session(rpc);
 | 
			
		||||
        session = rpc_session_open(rpc);
 | 
			
		||||
        delay(1);
 | 
			
		||||
    }
 | 
			
		||||
    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");
 | 
			
		||||
    clean_directory(fs_api, TEST_DIR_NAME);
 | 
			
		||||
    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) {
 | 
			
		||||
@ -82,13 +97,7 @@ static void test_rpc_storage_teardown(void) {
 | 
			
		||||
    clean_directory(fs_api, TEST_DIR_NAME);
 | 
			
		||||
    furi_record_close("storage");
 | 
			
		||||
 | 
			
		||||
    rpc_close_session(session);
 | 
			
		||||
    furi_record_close("rpc");
 | 
			
		||||
    vStreamBufferDelete(output_stream);
 | 
			
		||||
    ++command_id;
 | 
			
		||||
    output_stream = NULL;
 | 
			
		||||
    rpc = NULL;
 | 
			
		||||
    session = NULL;
 | 
			
		||||
    test_rpc_teardown();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
    uint32_t command_id) {
 | 
			
		||||
    furi_assert(message);
 | 
			
		||||
    furi_assert(str);
 | 
			
		||||
 | 
			
		||||
    char* str_copy = furi_alloc(strlen(str) + 1);
 | 
			
		||||
    char* str_copy = NULL;
 | 
			
		||||
    if(str) {
 | 
			
		||||
        str_copy = furi_alloc(strlen(str) + 1);
 | 
			
		||||
        strcpy(str_copy, str);
 | 
			
		||||
    }
 | 
			
		||||
    message->command_id = command_id;
 | 
			
		||||
    message->command_status = PB_CommandStatus_OK;
 | 
			
		||||
    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;
 | 
			
		||||
    uint8_t* buffer_ptr = buffer;
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(
 | 
			
		||||
    MsgList_t msg_list,
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
    test_rpc_create_simple_message(&request, PB_Main_storage_list_request_tag, 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_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) {
 | 
			
		||||
    test_rpc_storage_list_run("/", ++command_id);
 | 
			
		||||
    test_rpc_storage_list_run("/ext/nfc", ++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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
    Storage* fs_api = furi_record_open("storage");
 | 
			
		||||
    FS_Error error = storage_common_mkdir(fs_api, path);
 | 
			
		||||
    (void)error;
 | 
			
		||||
    furi_assert((error == FSE_OK) || (error == FSE_EXIST));
 | 
			
		||||
    furi_record_close("storage");
 | 
			
		||||
    furi_check(test_is_exists(path));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
    furi_record_close("storage");
 | 
			
		||||
    furi_check(test_is_exists(path));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
    MsgList_t expected_msg_list;
 | 
			
		||||
    MsgList_init(expected_msg_list);
 | 
			
		||||
 | 
			
		||||
    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_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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MU_TEST(test_storage_delete) {
 | 
			
		||||
    test_create_file(TEST_DIR "empty.txt", 0);
 | 
			
		||||
    test_storage_delete_run(TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_OK);
 | 
			
		||||
    test_storage_delete_run(
 | 
			
		||||
        TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_ERROR_STORAGE_NOT_EXIST);
 | 
			
		||||
#define TEST_DIR_RMRF_NAME TEST_DIR "rmrf_test"
 | 
			
		||||
#define TEST_DIR_RMRF TEST_DIR_RMRF_NAME "/"
 | 
			
		||||
MU_TEST(test_storage_delete_recursive) {
 | 
			
		||||
    test_create_dir(TEST_DIR_RMRF_NAME);
 | 
			
		||||
 | 
			
		||||
    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_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) {
 | 
			
		||||
@ -872,18 +990,17 @@ static void test_storage_mkdir_run(const char* path, size_t command_id, PB_Comma
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
    mu_check(test_is_exists(TEST_DIR "dir1"));
 | 
			
		||||
 | 
			
		||||
    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_storage_mkdir_run(TEST_DIR "dir2", ++command_id, PB_CommandStatus_ERROR_STORAGE_EXIST);
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    mu_check(test_is_exists(TEST_DIR "dir2"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
@ -1026,6 +1143,7 @@ MU_TEST_SUITE(test_rpc_storage) {
 | 
			
		||||
    MU_RUN_TEST(test_storage_write_read);
 | 
			
		||||
    MU_RUN_TEST(test_storage_write);
 | 
			
		||||
    MU_RUN_TEST(test_storage_delete);
 | 
			
		||||
    MU_RUN_TEST(test_storage_delete_recursive);
 | 
			
		||||
    MU_RUN_TEST(test_storage_mkdir);
 | 
			
		||||
    MU_RUN_TEST(test_storage_md5sum);
 | 
			
		||||
    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);
 | 
			
		||||
    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);
 | 
			
		||||
    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);
 | 
			
		||||
    delay(100);
 | 
			
		||||
    test_app_get_status_lock_run(true, ++command_id);
 | 
			
		||||
    test_app_start_run(
 | 
			
		||||
        "Delay Test App", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id);
 | 
			
		||||
    test_app_start_run("Delay Test", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id);
 | 
			
		||||
    delay(200);
 | 
			
		||||
    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);
 | 
			
		||||
    test_app_get_status_lock_run(true, ++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_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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int run_minunit_test_rpc() {
 | 
			
		||||
    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_app);
 | 
			
		||||
    MU_REPORT();
 | 
			
		||||
 | 
			
		||||
    return MU_EXIT_CODE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -7,10 +7,27 @@
 | 
			
		||||
#include <cli/cli.h>
 | 
			
		||||
#include <loader/loader.h>
 | 
			
		||||
 | 
			
		||||
#define TESTS_TAG "UNIT_TESTS"
 | 
			
		||||
 | 
			
		||||
int run_minunit();
 | 
			
		||||
int run_minunit_test_irda_decoder_encoder();
 | 
			
		||||
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) {
 | 
			
		||||
    uint32_t test_result = 0;
 | 
			
		||||
    minunit_run = 0;
 | 
			
		||||
@ -25,21 +42,30 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) {
 | 
			
		||||
    furi_record_close("notification");
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    } else {
 | 
			
		||||
        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_irda_decoder_encoder();
 | 
			
		||||
        test_result |= run_minunit_test_rpc();
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
            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 {
 | 
			
		||||
            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_StopSession, PB_StopSession, 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_NOT_IMPLEMENTED = 12, /* *< Functon not implemented */
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
@ -42,6 +43,10 @@ typedef struct _PB_Empty {
 | 
			
		||||
    char dummy_field;
 | 
			
		||||
} PB_Empty;
 | 
			
		||||
 | 
			
		||||
typedef struct _PB_StopSession { 
 | 
			
		||||
    char dummy_field;
 | 
			
		||||
} PB_StopSession;
 | 
			
		||||
 | 
			
		||||
typedef struct _PB_Main { 
 | 
			
		||||
    uint32_t command_id; 
 | 
			
		||||
    PB_CommandStatus command_status; 
 | 
			
		||||
@ -64,14 +69,15 @@ typedef struct _PB_Main {
 | 
			
		||||
        PB_App_Start app_start;
 | 
			
		||||
        PB_App_LockStatusRequest app_lock_status_request;
 | 
			
		||||
        PB_App_LockStatusResponse app_lock_status_response;
 | 
			
		||||
        PB_StopSession stop_session;
 | 
			
		||||
    } content; 
 | 
			
		||||
} PB_Main;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Helper constants for enums */
 | 
			
		||||
#define _PB_CommandStatus_MIN PB_CommandStatus_OK
 | 
			
		||||
#define _PB_CommandStatus_MAX PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED
 | 
			
		||||
#define _PB_CommandStatus_ARRAYSIZE ((PB_CommandStatus)(PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED+1))
 | 
			
		||||
#define _PB_CommandStatus_MAX PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY
 | 
			
		||||
#define _PB_CommandStatus_ARRAYSIZE ((PB_CommandStatus)(PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY+1))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
@ -80,8 +86,10 @@ extern "C" {
 | 
			
		||||
 | 
			
		||||
/* Initializer values for message structs */
 | 
			
		||||
#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_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}}
 | 
			
		||||
 | 
			
		||||
/* 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_lock_status_request_tag      17
 | 
			
		||||
#define PB_Main_app_lock_status_response_tag     18
 | 
			
		||||
#define PB_Main_stop_session_tag                 19
 | 
			
		||||
 | 
			
		||||
/* Struct field encoding specification for nanopb */
 | 
			
		||||
#define PB_Empty_FIELDLIST(X, a) \
 | 
			
		||||
@ -110,6 +119,11 @@ extern "C" {
 | 
			
		||||
#define PB_Empty_CALLBACK 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) \
 | 
			
		||||
X(a, STATIC,   SINGULAR, UINT32,   command_id,        1) \
 | 
			
		||||
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,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_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_DEFAULT NULL
 | 
			
		||||
#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_lock_status_request_MSGTYPE PB_App_LockStatusRequest
 | 
			
		||||
#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_StopSession_msg;
 | 
			
		||||
extern const pb_msgdesc_t PB_Main_msg;
 | 
			
		||||
 | 
			
		||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
 | 
			
		||||
#define PB_Empty_fields &PB_Empty_msg
 | 
			
		||||
#define PB_StopSession_fields &PB_StopSession_msg
 | 
			
		||||
#define PB_Main_fields &PB_Main_msg
 | 
			
		||||
 | 
			
		||||
/* Maximum encoded size of messages (where known) */
 | 
			
		||||
#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)
 | 
			
		||||
#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];};
 | 
			
		||||
 | 
			
		||||
@ -16,10 +16,6 @@ typedef enum _PB_Storage_File_FileType {
 | 
			
		||||
} PB_Storage_File_FileType;
 | 
			
		||||
 | 
			
		||||
/* Struct definitions */
 | 
			
		||||
typedef struct _PB_Storage_DeleteRequest { 
 | 
			
		||||
    char *path; 
 | 
			
		||||
} PB_Storage_DeleteRequest;
 | 
			
		||||
 | 
			
		||||
typedef struct _PB_Storage_ListRequest { 
 | 
			
		||||
    char *path; 
 | 
			
		||||
} PB_Storage_ListRequest;
 | 
			
		||||
@ -36,6 +32,11 @@ typedef struct _PB_Storage_ReadRequest {
 | 
			
		||||
    char *path; 
 | 
			
		||||
} PB_Storage_ReadRequest;
 | 
			
		||||
 | 
			
		||||
typedef struct _PB_Storage_DeleteRequest { 
 | 
			
		||||
    char *path; 
 | 
			
		||||
    bool recursive; 
 | 
			
		||||
} PB_Storage_DeleteRequest;
 | 
			
		||||
 | 
			
		||||
typedef struct _PB_Storage_File { 
 | 
			
		||||
    PB_Storage_File_FileType type; 
 | 
			
		||||
    char *name; 
 | 
			
		||||
@ -81,7 +82,7 @@ extern "C" {
 | 
			
		||||
#define PB_Storage_ReadRequest_init_default      {NULL}
 | 
			
		||||
#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_DeleteRequest_init_default    {NULL}
 | 
			
		||||
#define PB_Storage_DeleteRequest_init_default    {NULL, 0}
 | 
			
		||||
#define PB_Storage_MkdirRequest_init_default     {NULL}
 | 
			
		||||
#define PB_Storage_Md5sumRequest_init_default    {NULL}
 | 
			
		||||
#define PB_Storage_Md5sumResponse_init_default   {""}
 | 
			
		||||
@ -91,17 +92,18 @@ extern "C" {
 | 
			
		||||
#define PB_Storage_ReadRequest_init_zero         {NULL}
 | 
			
		||||
#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_DeleteRequest_init_zero       {NULL}
 | 
			
		||||
#define PB_Storage_DeleteRequest_init_zero       {NULL, 0}
 | 
			
		||||
#define PB_Storage_MkdirRequest_init_zero        {NULL}
 | 
			
		||||
#define PB_Storage_Md5sumRequest_init_zero       {NULL}
 | 
			
		||||
#define PB_Storage_Md5sumResponse_init_zero      {""}
 | 
			
		||||
 | 
			
		||||
/* 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_Md5sumRequest_path_tag        1
 | 
			
		||||
#define PB_Storage_MkdirRequest_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_name_tag                 2
 | 
			
		||||
#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_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_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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct Version* furi_hal_version_get_boot_version(void) {
 | 
			
		||||
const struct Version* furi_hal_version_get_bootloader_version(void) {
 | 
			
		||||
#ifdef NO_BOOTLOADER
 | 
			
		||||
    return 0;
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
@ -272,7 +272,7 @@ const struct Version* furi_hal_version_get_firmware_version(void) {
 | 
			
		||||
    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
 | 
			
		||||
    return 0;
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
@ -148,7 +148,7 @@ const uint8_t* furi_hal_version_get_ble_mac();
 | 
			
		||||
 *
 | 
			
		||||
 * @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.
 | 
			
		||||
 *
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,7 @@ static void flipper_print_version(const char* target, const Version* version) {
 | 
			
		||||
void flipper_init() {
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
    version = (const Version*)furi_hal_version_get_firmware_version();
 | 
			
		||||
 | 
			
		||||
@ -85,6 +85,7 @@ bool furi_record_destroy(const char* name) {
 | 
			
		||||
    furi_assert(record_data);
 | 
			
		||||
    if(record_data->holders_count == 0) {
 | 
			
		||||
        FuriRecordDataDict_erase(furi_record->records, name_str);
 | 
			
		||||
        furi_check(osOK == osEventFlagsDelete(record_data->flags));
 | 
			
		||||
        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