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,13 +56,13 @@ static const uint8_t enclave_signature_expected[ENCLAVE_SIGNATURE_KEY_SLOTS][ENC
|
||||
*/
|
||||
void cli_command_device_info(Cli* cli, string_t args, void* context) {
|
||||
// Device Info version
|
||||
printf("device_info_major : %d\r\n", 1);
|
||||
printf("device_info_minor : %d\r\n", 0);
|
||||
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());
|
||||
printf("hardware_model : %s\r\n", furi_hal_version_get_model_name());
|
||||
|
||||
// Unique ID
|
||||
printf("hardware_uid : ");
|
||||
printf("hardware_uid : ");
|
||||
const uint8_t* uid = furi_hal_version_uid();
|
||||
for(size_t i = 0; i < furi_hal_version_uid_size(); i++) {
|
||||
printf("%02X", uid[i]);
|
||||
@ -70,69 +70,69 @@ void cli_command_device_info(Cli* cli, string_t args, void* context) {
|
||||
printf("\r\n");
|
||||
|
||||
// OTP Revision
|
||||
printf("hardware_otp_ver : %d\r\n", furi_hal_version_get_otp_version());
|
||||
printf("hardware_timestamp : %lu\r\n", furi_hal_version_get_hw_timestamp());
|
||||
printf("hardware_otp_ver : %d\r\n", furi_hal_version_get_otp_version());
|
||||
printf("hardware_timestamp : %lu\r\n", furi_hal_version_get_hw_timestamp());
|
||||
|
||||
// Board Revision
|
||||
printf("hardware_ver : %d\r\n", furi_hal_version_get_hw_version());
|
||||
printf("hardware_target : %d\r\n", furi_hal_version_get_hw_target());
|
||||
printf("hardware_body : %d\r\n", furi_hal_version_get_hw_body());
|
||||
printf("hardware_connect : %d\r\n", furi_hal_version_get_hw_connect());
|
||||
printf("hardware_display : %d\r\n", furi_hal_version_get_hw_display());
|
||||
printf("hardware_ver : %d\r\n", furi_hal_version_get_hw_version());
|
||||
printf("hardware_target : %d\r\n", furi_hal_version_get_hw_target());
|
||||
printf("hardware_body : %d\r\n", furi_hal_version_get_hw_body());
|
||||
printf("hardware_connect : %d\r\n", furi_hal_version_get_hw_connect());
|
||||
printf("hardware_display : %d\r\n", furi_hal_version_get_hw_display());
|
||||
|
||||
// Board Personification
|
||||
printf("hardware_color : %d\r\n", furi_hal_version_get_hw_color());
|
||||
printf("hardware_region : %d\r\n", furi_hal_version_get_hw_region());
|
||||
printf("hardware_color : %d\r\n", furi_hal_version_get_hw_color());
|
||||
printf("hardware_region : %d\r\n", furi_hal_version_get_hw_region());
|
||||
const char* name = furi_hal_version_get_name_ptr();
|
||||
if(name) {
|
||||
printf("hardware_name : %s\r\n", name);
|
||||
printf("hardware_name : %s\r\n", name);
|
||||
}
|
||||
|
||||
// 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
|
||||
const Version* firmware_version = furi_hal_version_get_firmware_version();
|
||||
if(firmware_version) {
|
||||
printf("firmware_commit : %s\r\n", version_get_githash(firmware_version));
|
||||
printf("firmware_branch : %s\r\n", version_get_gitbranch(firmware_version));
|
||||
printf("firmware_branch_num : %s\r\n", version_get_gitbranchnum(firmware_version));
|
||||
printf("firmware_version : %s\r\n", version_get_version(firmware_version));
|
||||
printf("firmware_build_date : %s\r\n", version_get_builddate(firmware_version));
|
||||
printf("firmware_target : %d\r\n", version_get_target(firmware_version));
|
||||
printf("firmware_commit : %s\r\n", version_get_githash(firmware_version));
|
||||
printf("firmware_branch : %s\r\n", version_get_gitbranch(firmware_version));
|
||||
printf("firmware_branch_num : %s\r\n", version_get_gitbranchnum(firmware_version));
|
||||
printf("firmware_version : %s\r\n", version_get_version(firmware_version));
|
||||
printf("firmware_build_date : %s\r\n", version_get_builddate(firmware_version));
|
||||
printf("firmware_target : %d\r\n", version_get_target(firmware_version));
|
||||
}
|
||||
|
||||
WirelessFwInfo_t pWirelessInfo;
|
||||
if(furi_hal_bt_is_alive() && SHCI_GetWirelessFwInfo(&pWirelessInfo) == SHCI_Success) {
|
||||
printf("radio_alive : true\r\n");
|
||||
printf("radio_alive : true\r\n");
|
||||
// FUS Info
|
||||
printf("radio_fus_major : %d\r\n", pWirelessInfo.FusVersionMajor);
|
||||
printf("radio_fus_minor : %d\r\n", pWirelessInfo.FusVersionMinor);
|
||||
printf("radio_fus_sub : %d\r\n", pWirelessInfo.FusVersionSub);
|
||||
printf("radio_fus_sram2b : %dK\r\n", pWirelessInfo.FusMemorySizeSram2B);
|
||||
printf("radio_fus_sram2a : %dK\r\n", pWirelessInfo.FusMemorySizeSram2A);
|
||||
printf("radio_fus_flash : %dK\r\n", pWirelessInfo.FusMemorySizeFlash * 4);
|
||||
printf("radio_fus_major : %d\r\n", pWirelessInfo.FusVersionMajor);
|
||||
printf("radio_fus_minor : %d\r\n", pWirelessInfo.FusVersionMinor);
|
||||
printf("radio_fus_sub : %d\r\n", pWirelessInfo.FusVersionSub);
|
||||
printf("radio_fus_sram2b : %dK\r\n", pWirelessInfo.FusMemorySizeSram2B);
|
||||
printf("radio_fus_sram2a : %dK\r\n", pWirelessInfo.FusMemorySizeSram2A);
|
||||
printf("radio_fus_flash : %dK\r\n", pWirelessInfo.FusMemorySizeFlash * 4);
|
||||
// Stack Info
|
||||
printf("radio_stack_type : %d\r\n", pWirelessInfo.StackType);
|
||||
printf("radio_stack_major : %d\r\n", pWirelessInfo.VersionMajor);
|
||||
printf("radio_stack_minor : %d\r\n", pWirelessInfo.VersionMinor);
|
||||
printf("radio_stack_sub : %d\r\n", pWirelessInfo.VersionSub);
|
||||
printf("radio_stack_branch : %d\r\n", pWirelessInfo.VersionBranch);
|
||||
printf("radio_stack_release : %d\r\n", pWirelessInfo.VersionReleaseType);
|
||||
printf("radio_stack_sram2b : %dK\r\n", pWirelessInfo.MemorySizeSram2B);
|
||||
printf("radio_stack_sram2a : %dK\r\n", pWirelessInfo.MemorySizeSram2A);
|
||||
printf("radio_stack_sram1 : %dK\r\n", pWirelessInfo.MemorySizeSram1);
|
||||
printf("radio_stack_flash : %dK\r\n", pWirelessInfo.MemorySizeFlash * 4);
|
||||
printf("radio_stack_type : %d\r\n", pWirelessInfo.StackType);
|
||||
printf("radio_stack_major : %d\r\n", pWirelessInfo.VersionMajor);
|
||||
printf("radio_stack_minor : %d\r\n", pWirelessInfo.VersionMinor);
|
||||
printf("radio_stack_sub : %d\r\n", pWirelessInfo.VersionSub);
|
||||
printf("radio_stack_branch : %d\r\n", pWirelessInfo.VersionBranch);
|
||||
printf("radio_stack_release : %d\r\n", pWirelessInfo.VersionReleaseType);
|
||||
printf("radio_stack_sram2b : %dK\r\n", pWirelessInfo.MemorySizeSram2B);
|
||||
printf("radio_stack_sram2a : %dK\r\n", pWirelessInfo.MemorySizeSram2A);
|
||||
printf("radio_stack_sram1 : %dK\r\n", pWirelessInfo.MemorySizeSram1);
|
||||
printf("radio_stack_flash : %dK\r\n", pWirelessInfo.MemorySizeFlash * 4);
|
||||
// Mac address
|
||||
printf("radio_ble_mac : ");
|
||||
printf("radio_ble_mac : ");
|
||||
const uint8_t* ble_mac = furi_hal_version_get_ble_mac();
|
||||
for(size_t i = 0; i < 6; i++) {
|
||||
printf("%02X", ble_mac[i]);
|
||||
@ -154,12 +154,12 @@ void cli_command_device_info(Cli* cli, string_t args, void* context) {
|
||||
furi_hal_crypto_store_unload_key(key_slot + 1);
|
||||
}
|
||||
}
|
||||
printf("enclave_valid_keys : %d\r\n", enclave_valid_keys);
|
||||
printf("enclave_valid_keys : %d\r\n", enclave_valid_keys);
|
||||
printf(
|
||||
"enclave_valid : %s\r\n",
|
||||
"enclave_valid : %s\r\n",
|
||||
(enclave_valid_keys == ENCLAVE_SIGNATURE_KEY_SLOTS) ? "true" : "false");
|
||||
} else {
|
||||
printf("radio_alive : false\r\n");
|
||||
printf("radio_alive : false\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ Desktop* desktop_alloc() {
|
||||
desktop->debug_view = desktop_debug_alloc();
|
||||
desktop->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,27 +124,49 @@ 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) {
|
||||
desktop_locked_update_hint_timeout(locked_view);
|
||||
if(locked_with_pin) {
|
||||
press_time = osKernelGetTickCount();
|
||||
|
||||
if(event->key == InputKeyBack) {
|
||||
uint32_t press_time = osKernelGetTickCount();
|
||||
// check if pressed sequentially
|
||||
if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) {
|
||||
if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT * 3) {
|
||||
locked_view->lock_lastpress = press_time;
|
||||
locked_view->lock_count = 0;
|
||||
} else if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) {
|
||||
locked_view->lock_lastpress = press_time;
|
||||
locked_view->lock_count++;
|
||||
locked_view->callback(DesktopLockedEventInputReset, locked_view->context);
|
||||
}
|
||||
|
||||
if(locked_view->lock_count == UNLOCK_CNT) {
|
||||
locked_view->lock_count = 0;
|
||||
locked_view->callback(DesktopLockedEventUnlock, locked_view->context);
|
||||
locked_view->callback(event->key, locked_view->context);
|
||||
} else {
|
||||
desktop_locked_update_hint_timeout(locked_view);
|
||||
|
||||
if(event->key == InputKeyBack) {
|
||||
press_time = osKernelGetTickCount();
|
||||
// check if pressed sequentially
|
||||
if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) {
|
||||
locked_view->lock_lastpress = press_time;
|
||||
locked_view->lock_count++;
|
||||
}
|
||||
|
||||
if(locked_view->lock_count == UNLOCK_CNT) {
|
||||
locked_view->lock_count = 0;
|
||||
locked_view->callback(DesktopLockedEventUnlock, locked_view->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) {
|
||||
locked_view->lock_lastpress = press_time;
|
||||
locked_view->lock_count = 0;
|
||||
}
|
||||
}
|
||||
// All events consumed
|
||||
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);
|
||||
if(session->send_bytes_callback) {
|
||||
session->send_bytes_callback(
|
||||
session->send_bytes_context, buffer, ostream.bytes_written);
|
||||
}
|
||||
osMutexRelease(session->send_bytes_mutex);
|
||||
osMutexAcquire(session->callbacks_mutex, osWaitForever);
|
||||
if(session->send_bytes_callback) {
|
||||
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));
|
||||
xStreamBufferReset(rpc->stream);
|
||||
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);
|
||||
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 {
|
||||
response->command_id = request->command_id;
|
||||
response->which_content = PB_Main_storage_read_response_tag;
|
||||
response->command_status = PB_CommandStatus_OK;
|
||||
response->content.storage_read_response.has_file = true;
|
||||
response->content.storage_read_response.file.data =
|
||||
furi_alloc(PB_BYTES_ARRAY_T_ALLOCSIZE(MIN(size_left, MAX_DATA_SIZE)));
|
||||
uint8_t* buffer = response->content.storage_read_response.file.data->bytes;
|
||||
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;
|
||||
}
|
||||
} else if(error_remove == FSE_NOT_EXIST) {
|
||||
status = PB_CommandStatus_OK;
|
||||
} else {
|
||||
status = rpc_system_storage_get_error(error_remove);
|
||||
}
|
||||
}
|
||||
rpc_encode_and_send_empty(rpc_storage->rpc, request->command_id, status);
|
||||
|
||||
furi_record_close("storage");
|
||||
rpc_send_and_release_empty(rpc_storage->rpc, request->command_id, status);
|
||||
}
|
||||
|
||||
static void rpc_system_storage_mkdir_process(const PB_Main* request, void* context) {
|
||||
@ -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);
|
||||
@ -392,4 +457,4 @@ bool storage_simply_mkdir(Storage* storage, const char* path) {
|
||||
FS_Error result;
|
||||
result = storage_common_mkdir(storage, path);
|
||||
return result == FSE_OK || result == FSE_EXIST;
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,6 +240,14 @@ FS_Error storage_sd_status(Storage* api);
|
||||
*/
|
||||
bool storage_simply_remove(Storage* storage, const char* path);
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -250,4 +258,4 @@ bool storage_simply_mkdir(Storage* storage, const char* path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -163,15 +163,15 @@ static LFSData* storage_int_lfs_data_alloc() {
|
||||
|
||||
static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) {
|
||||
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;
|
||||
@ -16,4 +16,7 @@ ADD_SCENE(subghz, test_static, TestStatic)
|
||||
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, 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,16 +126,19 @@ void subghz_scene_receiver_config_on_enter(void* context) {
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, subghz_frequencies_text[value_index]);
|
||||
|
||||
item = variable_item_list_add(
|
||||
subghz->variable_item_list,
|
||||
"Hopping:",
|
||||
HOPPING_COUNT,
|
||||
subghz_scene_receiver_config_set_hopping_runing,
|
||||
subghz);
|
||||
value_index = subghz_scene_receiver_config_hopper_value_index(
|
||||
subghz->txrx->hopper_state, hopping_value, HOPPING_COUNT, subghz);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, hopping_text[value_index]);
|
||||
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
|
||||
SubghzCustomEventManagerSet) {
|
||||
item = variable_item_list_add(
|
||||
subghz->variable_item_list,
|
||||
"Hopping:",
|
||||
HOPPING_COUNT,
|
||||
subghz_scene_receiver_config_set_hopping_runing,
|
||||
subghz);
|
||||
value_index = subghz_scene_receiver_config_hopper_value_index(
|
||||
subghz->txrx->hopper_state, hopping_value, HOPPING_COUNT, subghz);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, hopping_text[value_index]);
|
||||
}
|
||||
|
||||
item = variable_item_list_add(
|
||||
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,39 +388,39 @@ __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 { \
|
||||
snprintf( \
|
||||
minunit_last_message, \
|
||||
MINUNIT_MESSAGE_LEN, \
|
||||
"%s failed:\n\t%s:%d: Expected result was not NULL", \
|
||||
__func__, \
|
||||
__FILE__, \
|
||||
__LINE__); \
|
||||
minunit_status = 1; \
|
||||
return; \
|
||||
#define mu_assert_null(result) \
|
||||
MU__SAFE_BLOCK( \
|
||||
minunit_assert++; if(result == NULL) { minunit_print_progress(); } else { \
|
||||
snprintf( \
|
||||
minunit_last_message, \
|
||||
MINUNIT_MESSAGE_LEN, \
|
||||
"%s failed:\n\t%s:%d: Expected result was not NULL", \
|
||||
__func__, \
|
||||
__FILE__, \
|
||||
__LINE__); \
|
||||
minunit_status = 1; \
|
||||
return; \
|
||||
})
|
||||
|
||||
#define mu_assert_not_null(result) \
|
||||
MU__SAFE_BLOCK( \
|
||||
minunit_assert++; if(result != NULL) { printf("."); } else { \
|
||||
snprintf( \
|
||||
minunit_last_message, \
|
||||
MINUNIT_MESSAGE_LEN, \
|
||||
"%s failed:\n\t%s:%d: Expected result was not NULL", \
|
||||
__func__, \
|
||||
__FILE__, \
|
||||
__LINE__); \
|
||||
minunit_status = 1; \
|
||||
return; \
|
||||
#define mu_assert_not_null(result) \
|
||||
MU__SAFE_BLOCK( \
|
||||
minunit_assert++; if(result != NULL) { minunit_print_progress(); } else { \
|
||||
snprintf( \
|
||||
minunit_last_message, \
|
||||
MINUNIT_MESSAGE_LEN, \
|
||||
"%s failed:\n\t%s:%d: Expected result was not NULL", \
|
||||
__func__, \
|
||||
__FILE__, \
|
||||
__LINE__); \
|
||||
minunit_status = 1; \
|
||||
return; \
|
||||
})
|
||||
|
||||
#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, \
|
||||
@ -603,4 +605,4 @@ __attribute__((unused)) static double mu_timer_cpu(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MINUNIT_MINUNIT_H */
|
||||
#endif /* MINUNIT_MINUNIT_H */
|
||||
|
||||
@ -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);
|
||||
strcpy(str_copy, str);
|
||||
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);
|
||||
test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id);
|
||||
if(!strcmp(path, "/")) {
|
||||
test_rpc_storage_list_create_expected_list_root(expected_msg_list, command_id);
|
||||
} else {
|
||||
test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id);
|
||||
}
|
||||
test_rpc_encode_and_feed_one(&request);
|
||||
test_rpc_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() {
|
||||
MU_RUN_SUITE(test_rpc_storage);
|
||||
Storage* storage = furi_record_open("storage");
|
||||
furi_record_close("storage");
|
||||
if(storage_sd_status(storage) != FSE_OK) {
|
||||
FURI_LOG_E("UNIT_TESTS", "SD card not mounted - skip storage tests");
|
||||
} else {
|
||||
MU_RUN_SUITE(test_rpc_storage);
|
||||
}
|
||||
|
||||
MU_RUN_SUITE(test_rpc_status);
|
||||
MU_RUN_SUITE(test_rpc_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