Merge branch 'dev' into release-candidate

This commit is contained in:
Aleksandr Kutuzov 2021-10-28 17:24:03 +03:00
commit f3603e3c04
175 changed files with 5426 additions and 1418 deletions

1
.gitattributes vendored
View File

@ -1,2 +1 @@
* text=auto

View File

@ -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'

View File

@ -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);

View File

@ -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) {

View File

@ -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);
}

View File

@ -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");
}
}

View File

@ -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;

View File

@ -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;
};

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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) {

View File

@ -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, "");
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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)

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);

View 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, "");
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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,

View File

@ -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);
}

View 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;
}
}

View 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
View 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;
}

View File

@ -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
View 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
View 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

View 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;
});
}

View 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

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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;
}

View File

@ -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(

View File

@ -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(

View File

@ -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");

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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) {

View 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);
}

View File

@ -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);

View File

@ -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) {

View File

@ -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));
}

View 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;
}
}

View File

@ -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

View File

@ -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");

View 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;

View File

@ -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)

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}

View 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);
}

View 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);
}

View 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;
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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) {

View File

@ -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);
}

View File

@ -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(

View File

@ -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",

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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) {

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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,

View 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;
}

View 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);

View File

@ -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;
});

View File

@ -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,

View File

@ -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;
}

View File

@ -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,

View File

@ -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");
}

View File

@ -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;
}

View File

@ -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 */

View File

@ -62,7 +62,6 @@ MU_TEST_SUITE(test_suite) {
int run_minunit() {
MU_RUN_SUITE(test_suite);
MU_REPORT();
return MU_EXIT_CODE;
}

View File

@ -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;
}

View File

@ -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");
}
}
}

View File

@ -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)

View File

@ -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];};

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.
*

View File

@ -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();

View File

@ -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