Merge pull request #178 from Next-Flip/yeet-lfs
Remove LFS, FuriEventLoop pt2, and more OFW merging
@@ -23,4 +23,4 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: "Check code formatting"
|
||||
run: ./fbt lint lint_py
|
||||
run: ./fbt lint_all
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
[submodule "lib/mlib"]
|
||||
path = lib/mlib
|
||||
url = https://github.com/P-p-H-d/mlib.git
|
||||
[submodule "lib/littlefs"]
|
||||
path = lib/littlefs
|
||||
url = https://github.com/littlefs-project/littlefs.git
|
||||
[submodule "lib/nanopb"]
|
||||
path = lib/nanopb
|
||||
url = https://github.com/nanopb/nanopb.git
|
||||
|
||||
@@ -1 +1 @@
|
||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/*
|
||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/*
|
||||
|
||||
@@ -1,21 +1,74 @@
|
||||
### Breaking Changes:
|
||||
- Desktop: Settings restructured due to removal of LFS / LittleFS Internal Storage
|
||||
- You might need to reconfigure Desktop Settings (PIN code, auto lock, show clock)
|
||||
- Desktop Keybinds should transfer correctly automatically
|
||||
|
||||
### Added:
|
||||
- Apps:
|
||||
- Tools: Key Copier (by @zinongli)
|
||||
- Sub-GHz: Music to Sub-GHz Radio (by @jamisonderek)
|
||||
- Settings: Show free flash amount in internal storage info (by @Willy-JL)
|
||||
- Services:
|
||||
- OFW: On SD insert load BT, Desktop, Dolphin, Expansion, Notification, Region files (by @gsurkov)
|
||||
- On SD insert also load Momentum Settings, Asset Packs, FindMy Flipper, NameSpoof, RGB Backlight, Power, SubGHz options, and migrate files (by @Willy-JL)
|
||||
- Furi: Re-enabled `FURI_TRACE` since LFS removal frees DFU, will get better crash messages with source code path (by @Willy-JL)
|
||||
- OFW: Sub-GHz: Add Dickert MAHS garage door protocol (by @OevreFlataeker)
|
||||
- OFW: RFID: Add GProxII support (by @BarTenderNZ)
|
||||
- OFW: iButton: Support ID writing (by @Astrrra)
|
||||
- OFW: FBT: Add `-Wundef` to compiler options (by @hedger)
|
||||
- OFW: GUI: Added a text input that only accepts full numbers (by @leedave)
|
||||
- FBT:
|
||||
- OFW: Add `-Wundef` to compiler options (by @hedger)
|
||||
- OFW: Ensure that all images conform specification (by @skyhawkillusions & @hedger)
|
||||
- Don't format images in external apps folder, only firmware (by @Willy-JL)
|
||||
|
||||
### Updated:
|
||||
- Apps:
|
||||
- Seader: Remove some optional asn1 fields (by @bettse)
|
||||
- NFC Playlist: Fix extension check and error messages (by @acegoal07)
|
||||
- BLE Spam: Can use 20ms advertising again with LFS gone (by @Willy-JL)
|
||||
- Seader: Remove some optional ASN1 fields, disable ASN1 debug (by @bettse)
|
||||
- NFC Playlist: Fix extension check and error messages, bugfixes and improvements (by @acegoal07)
|
||||
- ESP Flasher: Update Marauder bins to v1.0.0 (by @justcallmekoko)
|
||||
- Sub-GHz Bruteforcer: Fix one/two byte text (by @Willy-JL)
|
||||
- Pokemon Trading: Reset trade without affecting current pokemon, major refactoring (by @kbembedded)
|
||||
- T5577 Raw Writer: Code refactor, bugfixes and improvements (by @zinongli)
|
||||
- AirMouse: Fix compatibility with new firmwares (by @ginkage)
|
||||
- Flizzer Tracker: Fix app not responding to keypresses (by @LTVA1)
|
||||
- UHF RFID: Bugfixes, some refactoring, write modes (by @frux-c)
|
||||
- Authenticator: Fix BT keys filenames format (by @akopachov)
|
||||
- UL: UART Terminal: Configurable CRLF or newline mode (by @xMasterX)
|
||||
- UL: SubGHz Bruteforcer: App refactoring and code documentation (by @derskythe)
|
||||
- Various app fixes for `-Wundef` option (by @Willy-JL)
|
||||
- Many app fixes for deprecated `view_dispatcher_enable_queue()` (by @xMasterX & @Willy-JL)
|
||||
- BadKB: Lower BLE conn interval like base HID profile (by @Willy-JL)
|
||||
- Desktop: Refactor Keybinds, no more 63 character limit, only load when activated to save RAM (by @Willy-JL)
|
||||
- MNTM Settings: SubGHz frequency add screen uses new NumberInput view (by @Willy-JL)
|
||||
- GUI: Small tweaks to NumberInput to match Text and Byte, and for better usability (by @Willy-JL)
|
||||
- Settings:
|
||||
- Statusbar Clock and Left Handed options show in normal Settings app like OFW (by @Willy-JL)
|
||||
- Update list of keys files for forget all devices (by @Willy-JL)
|
||||
- Services:
|
||||
- Big cleanup of all services and settings handling, refactor lots old code (by @Willy-JL)
|
||||
- Update all settings paths to use equivalents like OFW or UL for better compatibility (by @Willy-JL)
|
||||
- Updater: Change to `resources.tar.gz` filename to avoid confusion with update `.tgz` (by @Willy-JL)
|
||||
- OFW: NFC: Refactor detected protocols list (by @Astrrra)
|
||||
- Furi:
|
||||
- OFW: FuriEventLoop Pt.2 with `Mutex` `Semaphore` `StreamBuffer`, refactor Power service (by @gsurkov)
|
||||
- OFW: Update string documentation (by @skotopes)
|
||||
- OFW: CCID: App refactor (by @kidbomb)
|
||||
- OFW: Furi: Update string documentation (by @skotopes)
|
||||
- OFW: FBT: Toolchain v39 (by @hedger)
|
||||
|
||||
### Fixed:
|
||||
- GUI: Fix Dark Mode after XOR canvas color, like in NFC dict attack (by @Willy-JL)
|
||||
- OFW: NFC: Fix plantain balance string (by @Astrrra)
|
||||
- GUI:
|
||||
- Fix Dark Mode after XOR canvas color, like in NFC dict attack (by @Willy-JL)
|
||||
- OFW: Make file extensions case-insensitive (by @gsurkov))
|
||||
- NFC:
|
||||
- OFW: Fix plantain balance string (by @Astrrra)
|
||||
- OFW: Now fifo size in ST25 chip is calculated properly (by @RebornedBrain)
|
||||
- OFW: Sub-GHz: Fix RPC status for ButtonRelease event (by @Skorpionm)
|
||||
- OFW: Infrared: Fix cumulative error in infrared signals (by @gsurkov)
|
||||
- OFW: Desktop: Separate callbacks for dolphin and storage subscriptions (by @skotopes)
|
||||
- OFW: FBT: Improved size validator for updater image (by @hedger)
|
||||
- OFW: JS: Ensure proper closure of variadic function in `mjs_array` (by @derskythe)
|
||||
|
||||
### Removed:
|
||||
- OFW: Storage: Remove LFS / LittleFS Internal Storage, all config on SD Card (by @skotopes & @gsurkov)
|
||||
- Storage: Remove `CFG_PATH()` and `.config/` folder, `INT_PATH()` is now on SD card at `.int/` due to LFS removal and should be used instead (by @Willy-JL)
|
||||
|
||||
@@ -322,7 +322,13 @@ firmware_env.Append(
|
||||
"SConstruct",
|
||||
"firmware.scons",
|
||||
"fbt_options.py",
|
||||
]
|
||||
],
|
||||
IMG_LINT_SOURCES=[
|
||||
# Image assets
|
||||
"applications",
|
||||
"!applications/external",
|
||||
"assets",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -359,6 +365,39 @@ distenv.PhonyTarget(
|
||||
PY_LINT_SOURCES=firmware_env["PY_LINT_SOURCES"],
|
||||
)
|
||||
|
||||
# Image assets linting
|
||||
distenv.PhonyTarget(
|
||||
"lint_img",
|
||||
[
|
||||
[
|
||||
"${PYTHON3}",
|
||||
"${FBT_SCRIPT_DIR}/imglint.py",
|
||||
"check",
|
||||
"${IMG_LINT_SOURCES}",
|
||||
"${ARGS}",
|
||||
]
|
||||
],
|
||||
IMG_LINT_SOURCES=firmware_env["IMG_LINT_SOURCES"],
|
||||
)
|
||||
|
||||
distenv.PhonyTarget(
|
||||
"format_img",
|
||||
[
|
||||
[
|
||||
"${PYTHON3}",
|
||||
"${FBT_SCRIPT_DIR}/imglint.py",
|
||||
"format",
|
||||
"${IMG_LINT_SOURCES}",
|
||||
"${ARGS}",
|
||||
]
|
||||
],
|
||||
IMG_LINT_SOURCES=firmware_env["IMG_LINT_SOURCES"],
|
||||
)
|
||||
|
||||
distenv.Alias("lint_all", ["lint", "lint_py", "lint_img"])
|
||||
distenv.Alias("format_all", ["format", "format_py", "format_img"])
|
||||
|
||||
|
||||
# Start Flipper CLI via PySerial's miniterm
|
||||
distenv.PhonyTarget(
|
||||
"cli",
|
||||
|
||||
@@ -5,45 +5,49 @@
|
||||
AccessorAppViewManager::AccessorAppViewManager() {
|
||||
event_queue = furi_message_queue_alloc(10, sizeof(AccessorEvent));
|
||||
|
||||
view_dispatcher = view_dispatcher_alloc();
|
||||
auto callback = cbc::obtain_connector(this, &AccessorAppViewManager::previous_view_callback);
|
||||
view_holder = view_holder_alloc();
|
||||
auto callback =
|
||||
cbc::obtain_connector(this, &AccessorAppViewManager::view_holder_back_callback);
|
||||
|
||||
// allocate views
|
||||
submenu = submenu_alloc();
|
||||
add_view(ViewType::Submenu, submenu_get_view(submenu));
|
||||
|
||||
popup = popup_alloc();
|
||||
add_view(ViewType::Popup, popup_get_view(popup));
|
||||
|
||||
// set back callback
|
||||
view_holder_set_back_callback(view_holder, callback, NULL);
|
||||
|
||||
gui = static_cast<Gui*>(furi_record_open(RECORD_GUI));
|
||||
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// set previous view callback for all views
|
||||
view_set_previous_callback(submenu_get_view(submenu), callback);
|
||||
view_set_previous_callback(popup_get_view(popup), callback);
|
||||
view_holder_attach_to_gui(view_holder, gui);
|
||||
}
|
||||
|
||||
AccessorAppViewManager::~AccessorAppViewManager() {
|
||||
// remove views
|
||||
view_dispatcher_remove_view(
|
||||
view_dispatcher, static_cast<uint32_t>(AccessorAppViewManager::ViewType::Submenu));
|
||||
view_dispatcher_remove_view(
|
||||
view_dispatcher, static_cast<uint32_t>(AccessorAppViewManager::ViewType::Popup));
|
||||
|
||||
// remove current view
|
||||
view_holder_set_view(view_holder, NULL);
|
||||
// free view modules
|
||||
furi_record_close(RECORD_GUI);
|
||||
submenu_free(submenu);
|
||||
popup_free(popup);
|
||||
|
||||
// free dispatcher
|
||||
view_dispatcher_free(view_dispatcher);
|
||||
|
||||
// free view holder
|
||||
view_holder_free(view_holder);
|
||||
// free event queue
|
||||
furi_message_queue_free(event_queue);
|
||||
}
|
||||
|
||||
void AccessorAppViewManager::switch_to(ViewType type) {
|
||||
view_dispatcher_switch_to_view(view_dispatcher, static_cast<uint32_t>(type));
|
||||
View* view;
|
||||
|
||||
switch(type) {
|
||||
case ViewType::Submenu:
|
||||
view = submenu_get_view(submenu);
|
||||
break;
|
||||
case ViewType::Popup:
|
||||
view = popup_get_view(popup);
|
||||
break;
|
||||
default:
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
view_holder_set_view(view_holder, view);
|
||||
}
|
||||
|
||||
Submenu* AccessorAppViewManager::get_submenu() {
|
||||
@@ -65,16 +69,10 @@ void AccessorAppViewManager::send_event(AccessorEvent* event) {
|
||||
furi_check(result == FuriStatusOk);
|
||||
}
|
||||
|
||||
uint32_t AccessorAppViewManager::previous_view_callback(void*) {
|
||||
void AccessorAppViewManager::view_holder_back_callback(void*) {
|
||||
if(event_queue != NULL) {
|
||||
AccessorEvent event;
|
||||
event.type = AccessorEvent::Type::Back;
|
||||
send_event(&event);
|
||||
}
|
||||
|
||||
return VIEW_IGNORE;
|
||||
}
|
||||
|
||||
void AccessorAppViewManager::add_view(ViewType view_type, View* view) {
|
||||
view_dispatcher_add_view(view_dispatcher, static_cast<uint32_t>(view_type), view);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/view_holder.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include "accessor_event.h"
|
||||
@@ -10,7 +10,6 @@ public:
|
||||
enum class ViewType : uint8_t {
|
||||
Submenu,
|
||||
Popup,
|
||||
Tune,
|
||||
};
|
||||
|
||||
FuriMessageQueue* event_queue;
|
||||
@@ -27,11 +26,10 @@ public:
|
||||
Popup* get_popup(void);
|
||||
|
||||
private:
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Gui* gui;
|
||||
ViewHolder* view_holder;
|
||||
|
||||
uint32_t previous_view_callback(void* context);
|
||||
void add_view(ViewType view_type, View* view);
|
||||
void view_holder_back_callback(void* context);
|
||||
|
||||
// view elements
|
||||
Submenu* submenu;
|
||||
|
||||
@@ -42,7 +42,6 @@ BatteryTestApp* battery_test_alloc(void) {
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, battery_test_battery_info_update_model, 500);
|
||||
|
||||
@@ -36,7 +36,6 @@ BtDebugApp* bt_debug_app_alloc(void) {
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Views
|
||||
|
||||
@@ -66,7 +66,6 @@ CrashTest* crash_test_alloc(void) {
|
||||
|
||||
instance->gui = furi_record_open(RECORD_GUI);
|
||||
instance->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(instance->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(
|
||||
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
|
||||
@@ -126,7 +126,6 @@ DisplayTest* display_test_alloc(void) {
|
||||
|
||||
instance->gui = furi_record_open(RECORD_GUI);
|
||||
instance->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(instance->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(
|
||||
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
|
||||
@@ -82,7 +82,8 @@ static void view_port_input_callback(InputEvent* input_event, void* context) {
|
||||
furi_message_queue_put(app->input_queue, input_event, 0);
|
||||
}
|
||||
|
||||
static bool input_queue_callback(FuriMessageQueue* queue, void* context) {
|
||||
static bool input_queue_callback(FuriEventLoopObject* object, void* context) {
|
||||
FuriMessageQueue* queue = object;
|
||||
EventLoopBlinkTestApp* app = context;
|
||||
|
||||
InputEvent event;
|
||||
@@ -144,7 +145,7 @@ int32_t event_loop_blink_test_app(void* arg) {
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
furi_event_loop_tick_set(app.event_loop, 500, event_loop_tick_callback, &app);
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
app.event_loop, app.input_queue, FuriEventLoopEventIn, input_queue_callback, &app);
|
||||
|
||||
furi_event_loop_run(app.event_loop);
|
||||
@@ -154,7 +155,7 @@ int32_t event_loop_blink_test_app(void* arg) {
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
furi_event_loop_message_queue_unsubscribe(app.event_loop, app.input_queue);
|
||||
furi_event_loop_unsubscribe(app.event_loop, app.input_queue);
|
||||
furi_message_queue_free(app.input_queue);
|
||||
|
||||
for(size_t i = 0; i < TIMER_COUNT; ++i) {
|
||||
|
||||
@@ -33,8 +33,6 @@ FileBrowserApp* file_browser_app_alloc(char* arg) {
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
app->scene_manager = scene_manager_alloc(&file_browser_scene_handlers, app);
|
||||
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
|
Before Width: | Height: | Size: 576 B After Width: | Height: | Size: 96 B |
@@ -19,7 +19,7 @@ bool file_browser_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
furi_string_set(app->file_path, ANY_PATH("badusb/Demos/demo_windows.txt"));
|
||||
furi_string_set(app->file_path, EXT_PATH("badusb/Demos/demo_windows.txt"));
|
||||
scene_manager_next_scene(app->scene_manager, FileBrowserSceneBrowser);
|
||||
consumed = true;
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
App(
|
||||
appid="infrared_test",
|
||||
name="Infrared Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="infrared_test_app",
|
||||
fap_category="Debug",
|
||||
targets=["f7"],
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal_infrared.h>
|
||||
|
||||
#define TAG "InfraredTest"
|
||||
|
||||
#define CARRIER_FREQ_HZ (38000UL)
|
||||
#define CARRIER_DUTY (0.33f)
|
||||
|
||||
#define BURST_DURATION_US (600UL)
|
||||
#define BURST_COUNT (50UL)
|
||||
|
||||
typedef struct {
|
||||
bool level;
|
||||
uint32_t count;
|
||||
} InfraredTestApp;
|
||||
|
||||
static FuriHalInfraredTxGetDataState
|
||||
infrared_test_app_tx_data_callback(void* context, uint32_t* duration, bool* level) {
|
||||
furi_assert(context);
|
||||
furi_assert(duration);
|
||||
furi_assert(level);
|
||||
|
||||
InfraredTestApp* app = context;
|
||||
|
||||
*duration = BURST_DURATION_US;
|
||||
*level = app->level;
|
||||
|
||||
app->level = !app->level;
|
||||
app->count += 1;
|
||||
|
||||
if(app->count < BURST_COUNT * 2) {
|
||||
return FuriHalInfraredTxGetDataStateOk;
|
||||
} else {
|
||||
return FuriHalInfraredTxGetDataStateLastDone;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t infrared_test_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
InfraredTestApp app = {
|
||||
.level = true,
|
||||
};
|
||||
|
||||
FURI_LOG_I(TAG, "Starting test signal on PA7");
|
||||
|
||||
furi_hal_infrared_set_tx_output(FuriHalInfraredTxPinExtPA7);
|
||||
furi_hal_infrared_async_tx_set_data_isr_callback(infrared_test_app_tx_data_callback, &app);
|
||||
furi_hal_infrared_async_tx_start(CARRIER_FREQ_HZ, CARRIER_DUTY);
|
||||
furi_hal_infrared_async_tx_wait_termination();
|
||||
furi_hal_infrared_set_tx_output(FuriHalInfraredTxPinInternal);
|
||||
|
||||
FURI_LOG_I(TAG, "Test signal end");
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"The measured signal should be %luus +-%.1fus",
|
||||
(app.count - 1) * BURST_DURATION_US,
|
||||
(double)1000000.0 / CARRIER_FREQ_HZ);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -17,7 +17,6 @@ static LfRfidDebug* lfrfid_debug_alloc(void) {
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&lfrfid_debug_scene_handlers, app);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, lfrfid_debug_custom_event_callback);
|
||||
|
||||
@@ -61,7 +61,6 @@ static LocaleTestApp* locale_test_alloc(void) {
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Views
|
||||
|
||||
@@ -99,7 +99,6 @@ static RpcDebugApp* rpc_debug_app_alloc(void) {
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, rpc_debug_app_tick_event_callback, 100);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
app->widget = widget_alloc();
|
||||
view_dispatcher_add_view(
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 301 B |
|
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 96 B |
@@ -30,7 +30,6 @@ SubGhzTestApp* subghz_test_app_alloc(void) {
|
||||
// View Dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&subghz_test_scene_handlers, app);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
|
||||
@@ -126,7 +126,6 @@ int32_t text_box_view_test_app(void* p) {
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
ViewDispatcher* view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_enable_queue(view_dispatcher);
|
||||
|
||||
TextBoxViewTest instance = {
|
||||
.text_box = text_box_alloc(),
|
||||
|
||||
@@ -242,7 +242,6 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Views
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Filetype: Flipper SubGhz Key File
|
||||
Version: 1
|
||||
Frequency: 433920000
|
||||
Preset: FuriHalSubGhzPresetOok650Async
|
||||
Protocol: Dickert_MAHS
|
||||
Bit: 36
|
||||
Key: 00 00 00 01 55 57 55 15
|
||||
@@ -0,0 +1,7 @@
|
||||
Filetype: Flipper SubGhz RAW File
|
||||
Version: 1
|
||||
Frequency: 433920000
|
||||
Preset: FuriHalSubGhzPresetOok650Async
|
||||
Protocol: RAW
|
||||
RAW_Data: 112254 -62882 64 -8912 798 -844 416 -418 806 -850 396 -45206 440 -428 794 -442 804 -422 822 -810 414 -414 824 -832 412 -416 808 -848 376 -446 792 -846 382 -448 816 -828 410 -416 810 -844 382 -416 834 -818 410 -414 810 -856 408 -810 412 -836 384 -442 808 -814 402 -844 414 -834 378 -436 808 -844 396 -422 798 -844 416 -416 814 -404 812 -440 810 -842 396 -422 798 -840 414 -414 806 -850 398 -45210 450 -420 796 -436 780 -446 802 -848 380 -434 806 -846 400 -422 800 -840 410 -408 836 -812 414 -410 826 -840 378 -440 804 -848 396 -426 812 -810 426 -394 826 -844 414 -810 420 -834 378 -442 808 -832 412 -812 416 -830 410 -406 810 -844 400 -420 832 -810 414 -416 800 -446 798 -440 812 -808 426 -410 800 -836 412 -414 806 -836 412 -45216 450 -420 798 -434 806 -414 802 -846 382 -438 814 -832 410 -410 838 -834 396 -430 810 -842 394 -392 826 -840 414 -414 802 -850 396 -428 812 -842 394 -394 828 -842 414 -810 424 -812 392 -434 812 -844 398 -848 380 -844 408 -416 820 -810 414 -406 816 -836 412 -416 836 -414 816 -398 816 -840 420 -410 802 -844 416 -416 804 -824 410 -45232 446 -400 802 -442 810 -432 804 -842 396 -392 826 -842 410 -410 834 -818 378 -442 804 -854 406 -408 806 -838 408 -428 804 -844 396 -392 826 -840 410 -410 834 -810 414 -832 408 -834 380 -440 802 -826 410 -836 412 -838 396 -424 796 -842 414 -414 804 -848 396 -426 812 -412 814 -414 824 -832 410 -416 806 -848 382 -420 834 -814 422 -45228 416 -422 802 -446 810 -420 790 -846 382 -448 818 -828 408 -416 808 -848 382 -418 830 -816 410 -412 812 -856 410 -382 834 -846 382 -418 832 -818 408 -412 812 -856 408 -814 414 -838 396 -428 810 -808 424 -836 380 -844 404 -416 802 -840 424 -394 826 -840 414 -382 836 -412 822 -436 812 -806 424 -394 826 -844 416 -382 838 -816 402 -45228 438 -430 796 -444 806 -424 822 -810 412 -416 822 -832 412 -416 804 -844 408 -414 824 -812 412 -408 812 -834 410 -414 804 -848 408 -412 802 -840 424 -412 802 -834 412 -842 384 -848 396 -426 814 -808 424 -816 392 -866 382 -414 838 -816 414 -428 792 -846 380 -440 810 -438 812 -412 802 -846 380 -438 826 -840 380 -416 838 -814 404 -45226 450 -404 820 -408 806 -452 792 -848 382 -440 814 -832 410 -416 810 -846 378 -450 792 -846 380 -446 816 -830 410 -386 836 -846 376 -410 828 -846 380 -446 814 -828 410 -814 414 -836 396 -428 810 -842 394 -816 410 -836 406 -430 812 -810 426 -394 826 -838
|
||||
RAW_Data: 414 -414 808 -416 826 -438 814 -816 420 -414 834 -814 418 -418 808 -848 398 -45218 412 -438 824 -412 812 -418 832 -852 378 -446 782 -862 410 -386 838 -848 384 -420 836 -820 418 -414 814 -854 408 -388 838 -814 418 -422 836 -816 394 -434 812 -846 398 -850 380 -848 410 -418 822 -812 416 -850 368 -854 412 -418 810 -850 384 -422 834 -820 416 -414 812 -428 836 -412 804 -848 382 -450 818 -828 412 -418 808 -850 380 -45228 452 -420 798 -434 806 -416 834 -818 384 -440 810 -820 404 -420 834 -814 416 -418 834 -824 386 -442 810 -818 404 -420 834 -814 416 -418 834 -820 410 -414 810 -850 406 -812 414 -816 404 -420 818 -838 386 -848 394 -828 414 -414 838 -814 406 -420 820 -842 384 -446 794 -438 810 -412 802 -848 394 -432 812 -842 394 -392 830 -842 414 -105578 64 -1760 130 -196 130 -832 160 -128 62 -1278 194 -1316 230 -96 362 -64 64 -398
|
||||
@@ -19,25 +19,24 @@ typedef struct {
|
||||
uint32_t consumer_counter;
|
||||
} TestFuriData;
|
||||
|
||||
bool test_furi_event_loop_producer_mq_callback(FuriMessageQueue* queue, void* context) {
|
||||
bool test_furi_event_loop_producer_mq_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriData* data = context;
|
||||
furi_check(data->mq == queue, "Invalid queue");
|
||||
furi_check(data->mq == object, "Invalid queue");
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG, "producer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
|
||||
|
||||
// Remove and add should not cause crash
|
||||
// if(data->producer_counter == EVENT_LOOP_EVENT_COUNT/2) {
|
||||
// furi_event_loop_message_queue_remove(data->producer_event_loop, data->mq);
|
||||
// furi_event_loop_message_queue_add(
|
||||
// data->producer_event_loop,
|
||||
// data->mq,
|
||||
// FuriEventLoopEventOut,
|
||||
// test_furi_event_loop_producer_mq_callback,
|
||||
// data);
|
||||
// }
|
||||
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->producer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_mq_callback,
|
||||
data);
|
||||
}
|
||||
|
||||
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT) {
|
||||
furi_event_loop_stop(data->producer_event_loop);
|
||||
@@ -61,7 +60,7 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||
FURI_LOG_I(TAG, "producer start 1st run");
|
||||
|
||||
data->producer_event_loop = furi_event_loop_alloc();
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->producer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventOut,
|
||||
@@ -73,7 +72,7 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
||||
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
||||
|
||||
furi_event_loop_message_queue_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_free(data->producer_event_loop);
|
||||
|
||||
FURI_LOG_I(TAG, "producer start 2nd run");
|
||||
@@ -81,7 +80,7 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||
data->producer_counter = 0;
|
||||
data->producer_event_loop = furi_event_loop_alloc();
|
||||
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->producer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventOut,
|
||||
@@ -90,7 +89,7 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||
|
||||
furi_event_loop_run(data->producer_event_loop);
|
||||
|
||||
furi_event_loop_message_queue_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_free(data->producer_event_loop);
|
||||
|
||||
FURI_LOG_I(TAG, "producer end");
|
||||
@@ -98,11 +97,11 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool test_furi_event_loop_consumer_mq_callback(FuriMessageQueue* queue, void* context) {
|
||||
bool test_furi_event_loop_consumer_mq_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriData* data = context;
|
||||
furi_check(data->mq == queue);
|
||||
furi_check(data->mq == object);
|
||||
|
||||
furi_delay_us(furi_hal_random_get() % 1000);
|
||||
furi_check(furi_message_queue_get(data->mq, &data->consumer_counter, 0) == FuriStatusOk);
|
||||
@@ -110,16 +109,15 @@ bool test_furi_event_loop_consumer_mq_callback(FuriMessageQueue* queue, void* co
|
||||
FURI_LOG_I(
|
||||
TAG, "consumer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
|
||||
|
||||
// Remove and add should not cause crash
|
||||
// if(data->producer_counter == EVENT_LOOP_EVENT_COUNT/2) {
|
||||
// furi_event_loop_message_queue_remove(data->consumer_event_loop, data->mq);
|
||||
// furi_event_loop_message_queue_add(
|
||||
// data->consumer_event_loop,
|
||||
// data->mq,
|
||||
// FuriEventLoopEventIn,
|
||||
// test_furi_event_loop_producer_mq_callback,
|
||||
// data);
|
||||
// }
|
||||
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->consumer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_mq_callback,
|
||||
data);
|
||||
}
|
||||
|
||||
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT) {
|
||||
furi_event_loop_stop(data->consumer_event_loop);
|
||||
@@ -137,7 +135,7 @@ int32_t test_furi_event_loop_consumer(void* p) {
|
||||
FURI_LOG_I(TAG, "consumer start 1st run");
|
||||
|
||||
data->consumer_event_loop = furi_event_loop_alloc();
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->consumer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventIn,
|
||||
@@ -149,14 +147,14 @@ int32_t test_furi_event_loop_consumer(void* p) {
|
||||
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
||||
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
||||
|
||||
furi_event_loop_message_queue_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_free(data->consumer_event_loop);
|
||||
|
||||
FURI_LOG_I(TAG, "consumer start 2nd run");
|
||||
|
||||
data->consumer_counter = 0;
|
||||
data->consumer_event_loop = furi_event_loop_alloc();
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->consumer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventIn,
|
||||
@@ -165,7 +163,7 @@ int32_t test_furi_event_loop_consumer(void* p) {
|
||||
|
||||
furi_event_loop_run(data->consumer_event_loop);
|
||||
|
||||
furi_event_loop_message_queue_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_free(data->consumer_event_loop);
|
||||
|
||||
FURI_LOG_I(TAG, "consumer end");
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <loader/loader.h>
|
||||
#include <storage/filesystem_api_defines.h>
|
||||
|
||||
#include <lib/toolbox/api_lock.h>
|
||||
#include <lib/toolbox/md5_calc.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
|
||||
@@ -35,8 +36,8 @@ static uint32_t command_id = 0;
|
||||
typedef struct {
|
||||
RpcSession* session;
|
||||
FuriStreamBuffer* output_stream;
|
||||
FuriSemaphore* close_session_semaphore;
|
||||
FuriSemaphore* terminate_semaphore;
|
||||
FuriApiLock session_close_lock;
|
||||
FuriApiLock session_terminate_lock;
|
||||
uint32_t timeout;
|
||||
} RpcSessionContext;
|
||||
|
||||
@@ -92,8 +93,8 @@ static void test_rpc_setup(void) {
|
||||
|
||||
rpc_session[0].output_stream = furi_stream_buffer_alloc(4096, 1);
|
||||
rpc_session_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback);
|
||||
rpc_session[0].close_session_semaphore = furi_semaphore_alloc(1, 0);
|
||||
rpc_session[0].terminate_semaphore = furi_semaphore_alloc(1, 0);
|
||||
rpc_session[0].session_close_lock = api_lock_alloc_locked();
|
||||
rpc_session[0].session_terminate_lock = api_lock_alloc_locked();
|
||||
rpc_session_set_close_callback(rpc_session[0].session, test_rpc_session_close_callback);
|
||||
rpc_session_set_terminated_callback(
|
||||
rpc_session[0].session, test_rpc_session_terminated_callback);
|
||||
@@ -112,8 +113,8 @@ static void test_rpc_setup_second_session(void) {
|
||||
|
||||
rpc_session[1].output_stream = furi_stream_buffer_alloc(1000, 1);
|
||||
rpc_session_set_send_bytes_callback(rpc_session[1].session, output_bytes_callback);
|
||||
rpc_session[1].close_session_semaphore = furi_semaphore_alloc(1, 0);
|
||||
rpc_session[1].terminate_semaphore = furi_semaphore_alloc(1, 0);
|
||||
rpc_session[1].session_close_lock = api_lock_alloc_locked();
|
||||
rpc_session[1].session_terminate_lock = api_lock_alloc_locked();
|
||||
rpc_session_set_close_callback(rpc_session[1].session, test_rpc_session_close_callback);
|
||||
rpc_session_set_terminated_callback(
|
||||
rpc_session[1].session, test_rpc_session_terminated_callback);
|
||||
@@ -121,36 +122,32 @@ static void test_rpc_setup_second_session(void) {
|
||||
}
|
||||
|
||||
static void test_rpc_teardown(void) {
|
||||
furi_check(rpc_session[0].close_session_semaphore);
|
||||
furi_semaphore_acquire(rpc_session[0].terminate_semaphore, 0);
|
||||
furi_check(rpc_session[0].session_close_lock);
|
||||
api_lock_relock(rpc_session[0].session_terminate_lock);
|
||||
rpc_session_close(rpc_session[0].session);
|
||||
furi_check(
|
||||
furi_semaphore_acquire(rpc_session[0].terminate_semaphore, FuriWaitForever) ==
|
||||
FuriStatusOk);
|
||||
api_lock_wait_unlock(rpc_session[0].session_terminate_lock);
|
||||
furi_record_close(RECORD_RPC);
|
||||
furi_stream_buffer_free(rpc_session[0].output_stream);
|
||||
furi_semaphore_free(rpc_session[0].close_session_semaphore);
|
||||
furi_semaphore_free(rpc_session[0].terminate_semaphore);
|
||||
api_lock_free(rpc_session[0].session_close_lock);
|
||||
api_lock_free(rpc_session[0].session_terminate_lock);
|
||||
++command_id;
|
||||
rpc_session[0].output_stream = NULL;
|
||||
rpc_session[0].close_session_semaphore = NULL;
|
||||
rpc_session[0].session_close_lock = NULL;
|
||||
rpc = NULL;
|
||||
rpc_session[0].session = NULL;
|
||||
}
|
||||
|
||||
static void test_rpc_teardown_second_session(void) {
|
||||
furi_check(rpc_session[1].close_session_semaphore);
|
||||
furi_semaphore_acquire(rpc_session[1].terminate_semaphore, 0);
|
||||
furi_check(rpc_session[1].session_close_lock);
|
||||
api_lock_relock(rpc_session[1].session_terminate_lock);
|
||||
rpc_session_close(rpc_session[1].session);
|
||||
furi_check(
|
||||
furi_semaphore_acquire(rpc_session[1].terminate_semaphore, FuriWaitForever) ==
|
||||
FuriStatusOk);
|
||||
api_lock_wait_unlock(rpc_session[1].session_terminate_lock);
|
||||
furi_stream_buffer_free(rpc_session[1].output_stream);
|
||||
furi_semaphore_free(rpc_session[1].close_session_semaphore);
|
||||
furi_semaphore_free(rpc_session[1].terminate_semaphore);
|
||||
api_lock_free(rpc_session[1].session_close_lock);
|
||||
api_lock_free(rpc_session[1].session_terminate_lock);
|
||||
++command_id;
|
||||
rpc_session[1].output_stream = NULL;
|
||||
rpc_session[1].close_session_semaphore = NULL;
|
||||
rpc_session[1].session_close_lock = NULL;
|
||||
rpc_session[1].session = NULL;
|
||||
}
|
||||
|
||||
@@ -204,14 +201,14 @@ static void test_rpc_session_close_callback(void* context) {
|
||||
furi_check(context);
|
||||
RpcSessionContext* callbacks_context = context;
|
||||
|
||||
furi_check(furi_semaphore_release(callbacks_context->close_session_semaphore) == FuriStatusOk);
|
||||
api_lock_unlock(callbacks_context->session_close_lock);
|
||||
}
|
||||
|
||||
static void test_rpc_session_terminated_callback(void* context) {
|
||||
furi_check(context);
|
||||
RpcSessionContext* callbacks_context = context;
|
||||
|
||||
furi_check(furi_semaphore_release(callbacks_context->terminate_semaphore) == FuriStatusOk);
|
||||
api_lock_unlock(callbacks_context->session_terminate_lock);
|
||||
}
|
||||
|
||||
static void test_rpc_print_message_list(MsgList_t msg_list) {
|
||||
@@ -1645,7 +1642,7 @@ static void test_rpc_feed_rubbish_run(
|
||||
|
||||
test_rpc_add_empty_to_list(expected, PB_CommandStatus_ERROR_DECODE, 0);
|
||||
|
||||
furi_check(furi_semaphore_acquire(rpc_session[0].close_session_semaphore, 0) != FuriStatusOk);
|
||||
furi_check(api_lock_is_locked(rpc_session[0].session_close_lock));
|
||||
test_rpc_encode_and_feed(input_before, 0);
|
||||
test_send_rubbish(rpc_session[0].session, pattern, pattern_size, size);
|
||||
test_rpc_encode_and_feed(input_after, 0);
|
||||
|
||||
@@ -663,6 +663,13 @@ MU_TEST(subghz_decoder_mastercode_test) {
|
||||
"Test decoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_dickert_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
EXT_PATH("unit_tests/subghz/dickert_raw.sub"), SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME " error\r\n");
|
||||
}
|
||||
|
||||
//test encoders
|
||||
MU_TEST(subghz_encoder_princeton_test) {
|
||||
mu_assert(
|
||||
@@ -827,6 +834,12 @@ MU_TEST(subghz_decoder_acurite_592txr_test) {
|
||||
"Test decoder " WS_PROTOCOL_ACURITE_592TXR_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_encoder_dickert_test) {
|
||||
mu_assert(
|
||||
subghz_encoder_test(EXT_PATH("unit_tests/subghz/dickert_mahs.sub")),
|
||||
"Test encoder " SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_random_test) {
|
||||
mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n");
|
||||
}
|
||||
@@ -878,6 +891,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_decoder_nice_one_test);
|
||||
MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test);
|
||||
MU_RUN_TEST(subghz_decoder_mastercode_test);
|
||||
MU_RUN_TEST(subghz_decoder_dickert_test);
|
||||
|
||||
MU_RUN_TEST(subghz_encoder_princeton_test);
|
||||
MU_RUN_TEST(subghz_encoder_came_test);
|
||||
@@ -906,6 +920,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_encoder_dooya_test);
|
||||
MU_RUN_TEST(subghz_encoder_mastercode_test);
|
||||
MU_RUN_TEST(subghz_decoder_acurite_592txr_test);
|
||||
MU_RUN_TEST(subghz_encoder_dickert_test);
|
||||
|
||||
MU_RUN_TEST(subghz_random_test);
|
||||
subghz_test_deinit();
|
||||
|
||||
@@ -36,14 +36,10 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(furi_event_loop_alloc, FuriEventLoop*, (void)),
|
||||
API_METHOD(furi_event_loop_free, void, (FuriEventLoop*)),
|
||||
API_METHOD(
|
||||
furi_event_loop_message_queue_subscribe,
|
||||
furi_event_loop_subscribe_message_queue,
|
||||
void,
|
||||
(FuriEventLoop*,
|
||||
FuriMessageQueue*,
|
||||
FuriEventLoopEvent,
|
||||
FuriEventLoopMessageQueueCallback,
|
||||
void*)),
|
||||
API_METHOD(furi_event_loop_message_queue_unsubscribe, void, (FuriEventLoop*, FuriMessageQueue*)),
|
||||
(FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*)),
|
||||
API_METHOD(furi_event_loop_unsubscribe, void, (FuriEventLoop*, FuriEventLoopObject*)),
|
||||
API_METHOD(furi_event_loop_run, void, (FuriEventLoop*)),
|
||||
API_METHOD(furi_event_loop_stop, void, (FuriEventLoop*)),
|
||||
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/run_parallel.h>
|
||||
|
||||
#include "test_runner.h"
|
||||
|
||||
@@ -13,21 +14,14 @@ void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
|
||||
test_runner_free(test_runner);
|
||||
}
|
||||
|
||||
static void unit_tests_pending(void* context, uint32_t arg) {
|
||||
UNUSED(arg);
|
||||
FuriThread* thread = context;
|
||||
furi_thread_join(thread);
|
||||
furi_thread_free(thread);
|
||||
}
|
||||
|
||||
static int32_t unit_tests_thread(void* context) {
|
||||
UNUSED(context);
|
||||
furi_delay_ms(5000);
|
||||
FuriString* args = furi_string_alloc();
|
||||
TestRunner* test_runner = test_runner_alloc(NULL, args);
|
||||
test_runner_run(test_runner);
|
||||
test_runner_free(test_runner);
|
||||
furi_string_free(args);
|
||||
furi_timer_pending_callback(unit_tests_pending, context, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -38,8 +32,6 @@ void unit_tests_on_system_start(void) {
|
||||
furi_record_close(RECORD_CLI);
|
||||
#endif
|
||||
if(furi_hal_is_normal_boot()) {
|
||||
FuriThread* thread = furi_thread_alloc_ex("UnitTests", 4 * 1024, unit_tests_thread, NULL);
|
||||
furi_thread_set_context(thread, thread);
|
||||
furi_thread_start(thread);
|
||||
run_parallel(unit_tests_thread, NULL, 4 * 1024);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,6 @@ UsbTestApp* usb_test_app_alloc(void) {
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Views
|
||||
|
||||
@@ -75,7 +75,6 @@ static BleBeaconApp* ble_beacon_app_alloc(void) {
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, ble_beacon_app_tick_event_callback, 100);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
app->submenu = submenu_alloc();
|
||||
view_dispatcher_add_view(
|
||||
|
||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 258 B |
@@ -0,0 +1,36 @@
|
||||
App(
|
||||
appid="example_event_loop_timer",
|
||||
name="Example: Event Loop Timer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
sources=["example_event_loop_timer.c"],
|
||||
entry_point="example_event_loop_timer_app",
|
||||
fap_category="Examples",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="example_event_loop_mutex",
|
||||
name="Example: Event Loop Mutex",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
sources=["example_event_loop_mutex.c"],
|
||||
entry_point="example_event_loop_mutex_app",
|
||||
fap_category="Examples",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="example_event_loop_stream_buffer",
|
||||
name="Example: Event Loop Stream Buffer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
sources=["example_event_loop_stream_buffer.c"],
|
||||
entry_point="example_event_loop_stream_buffer_app",
|
||||
fap_category="Examples",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="example_event_loop_multi",
|
||||
name="Example: Event Loop Multi",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
sources=["example_event_loop_multi.c"],
|
||||
entry_point="example_event_loop_multi_app",
|
||||
requires=["gui"],
|
||||
fap_category="Examples",
|
||||
)
|
||||
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* @file example_event_loop_multi.c
|
||||
* @brief Example application that demonstrates multiple primitives used with two FuriEventLoop instances.
|
||||
*
|
||||
* This application simulates a complex use case of having two concurrent event loops (each one executing in
|
||||
* its own thread) using a stream buffer for communication and additional timers and message passing to handle
|
||||
* the keypad input. Additionally, it shows how to use thread signals to stop an event loop in another thread.
|
||||
* The GUI functionality is there only for the purpose of exclusive access to the input events.
|
||||
*
|
||||
* The application's functionality consists of the following:
|
||||
* - Print keypad key names and types when pressed,
|
||||
* - If the Back key is long-pressed, a countdown starts upon completion of which the app exits,
|
||||
* - The countdown can be cancelled by long-pressing the Ok button, it also resets the counter,
|
||||
* - Blocks of random data are periodically generated in a separate thread,
|
||||
* - When ready, the main application thread gets notified and prints the data.
|
||||
*/
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_port.h>
|
||||
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "ExampleEventLoopMulti"
|
||||
|
||||
#define COUNTDOWN_START_VALUE (5UL)
|
||||
#define COUNTDOWN_INTERVAL_MS (1000UL)
|
||||
#define WORKER_DATA_INTERVAL_MS (1500UL)
|
||||
|
||||
#define INPUT_QUEUE_SIZE (8)
|
||||
#define STREAM_BUFFER_SIZE (16)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriEventLoopTimer* timer;
|
||||
FuriStreamBuffer* stream_buffer;
|
||||
} EventLoopMultiAppWorker;
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewPort* view_port;
|
||||
FuriThread* worker_thread;
|
||||
FuriEventLoop* event_loop;
|
||||
FuriMessageQueue* input_queue;
|
||||
FuriEventLoopTimer* exit_timer;
|
||||
FuriStreamBuffer* stream_buffer;
|
||||
uint32_t exit_countdown_value;
|
||||
} EventLoopMultiApp;
|
||||
|
||||
/*
|
||||
* Worker functions
|
||||
*/
|
||||
|
||||
// This function is executed each time the data is taken out of the stream buffer. It is used to restart the worker timer.
|
||||
static bool
|
||||
event_loop_multi_app_stream_buffer_worker_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiAppWorker* worker = context;
|
||||
|
||||
furi_assert(object == worker->stream_buffer);
|
||||
|
||||
FURI_LOG_I(TAG, "Data was removed from buffer");
|
||||
// Restart the timer to generate another block of random data.
|
||||
furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is executed when the worker timer expires. The timer will NOT restart automatically
|
||||
// since it is of one-shot type.
|
||||
static void event_loop_multi_app_worker_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiAppWorker* worker = context;
|
||||
|
||||
// Generate a block of random data.
|
||||
uint8_t data[STREAM_BUFFER_SIZE];
|
||||
furi_hal_random_fill_buf(data, sizeof(data));
|
||||
// Put the generated data in the stream buffer.
|
||||
// IMPORTANT: No waiting in the event handlers!
|
||||
furi_check(
|
||||
furi_stream_buffer_send(worker->stream_buffer, &data, sizeof(data), 0) == sizeof(data));
|
||||
}
|
||||
|
||||
static EventLoopMultiAppWorker*
|
||||
event_loop_multi_app_worker_alloc(FuriStreamBuffer* stream_buffer) {
|
||||
EventLoopMultiAppWorker* worker = malloc(sizeof(EventLoopMultiAppWorker));
|
||||
// Create the worker event loop.
|
||||
worker->event_loop = furi_event_loop_alloc();
|
||||
// Create the timer governing the data generation.
|
||||
// It is of one-shot type, i.e. it will not restart automatically upon expiration.
|
||||
worker->timer = furi_event_loop_timer_alloc(
|
||||
worker->event_loop,
|
||||
event_loop_multi_app_worker_timer_callback,
|
||||
FuriEventLoopTimerTypeOnce,
|
||||
worker);
|
||||
|
||||
// Using the same stream buffer as the main thread (it was already created beforehand).
|
||||
worker->stream_buffer = stream_buffer;
|
||||
// Notify the worker event loop about data being taken out of the stream buffer.
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
worker->event_loop,
|
||||
worker->stream_buffer,
|
||||
FuriEventLoopEventOut | FuriEventLoopEventFlagEdge,
|
||||
event_loop_multi_app_stream_buffer_worker_callback,
|
||||
worker);
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
||||
static void event_loop_multi_app_worker_free(EventLoopMultiAppWorker* worker) {
|
||||
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_unsubscribe(worker->event_loop, worker->stream_buffer);
|
||||
// IMPORTANT: All timers MUST be deleted before deleting the associated event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_timer_free(worker->timer);
|
||||
// Now it is okay to delete the event loop.
|
||||
furi_event_loop_free(worker->event_loop);
|
||||
|
||||
free(worker);
|
||||
}
|
||||
|
||||
static void event_loop_multi_app_worker_run(EventLoopMultiAppWorker* worker) {
|
||||
furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
|
||||
furi_event_loop_run(worker->event_loop);
|
||||
}
|
||||
|
||||
// This function is the worker thread body and (obviously) is executed in the worker thread.
|
||||
static int32_t event_loop_multi_app_worker_thread(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
|
||||
// Because an event loop is used, it MUST be created in the thread it will be run in.
|
||||
// Therefore, the worker creation and deletion is handled in the worker thread.
|
||||
EventLoopMultiAppWorker* worker = event_loop_multi_app_worker_alloc(app->stream_buffer);
|
||||
event_loop_multi_app_worker_run(worker);
|
||||
event_loop_multi_app_worker_free(worker);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Main application functions
|
||||
*/
|
||||
|
||||
// This function is executed in the GUI context each time an input event occurs (e.g. the user pressed a key)
|
||||
static void event_loop_multi_app_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
// Pass the event to the the application's input queue
|
||||
furi_check(furi_message_queue_put(app->input_queue, event, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
// This function is executed each time new data is available in the stream buffer.
|
||||
static bool
|
||||
event_loop_multi_app_stream_buffer_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
|
||||
furi_assert(object == app->stream_buffer);
|
||||
// Get the data from the stream buffer
|
||||
uint8_t data[STREAM_BUFFER_SIZE];
|
||||
// IMPORTANT: No waiting in the event handlers!
|
||||
furi_check(
|
||||
furi_stream_buffer_receive(app->stream_buffer, &data, sizeof(data), 0) == sizeof(data));
|
||||
|
||||
// Format the data for printing and print it to the debug output.
|
||||
FuriString* tmp_str = furi_string_alloc();
|
||||
for(uint32_t i = 0; i < sizeof(data); ++i) {
|
||||
furi_string_cat_printf(tmp_str, "%02X ", data[i]);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
|
||||
furi_string_free(tmp_str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is executed each time a new message is inserted in the input queue.
|
||||
static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
|
||||
furi_assert(object == app->input_queue);
|
||||
|
||||
InputEvent event;
|
||||
// IMPORTANT: No waiting in the event handlers!
|
||||
furi_check(furi_message_queue_get(app->input_queue, &event, 0) == FuriStatusOk);
|
||||
|
||||
if(event.type == InputTypeLong) {
|
||||
// The user has long-pressed the Back key, try starting the countdown.
|
||||
if(event.key == InputKeyBack) {
|
||||
if(!furi_event_loop_timer_is_running(app->exit_timer)) {
|
||||
// Actually start the countdown
|
||||
FURI_LOG_I(TAG, "Starting exit countdown!");
|
||||
furi_event_loop_timer_start(app->exit_timer, COUNTDOWN_INTERVAL_MS);
|
||||
|
||||
} else {
|
||||
// The countdown is already in progress, print a warning message
|
||||
FURI_LOG_W(TAG, "Countdown has already been started");
|
||||
}
|
||||
|
||||
// The user has long-pressed the Ok key, try stopping the countdown.
|
||||
} else if(event.key == InputKeyOk) {
|
||||
if(furi_event_loop_timer_is_running(app->exit_timer)) {
|
||||
// Actually cancel the countdown
|
||||
FURI_LOG_I(TAG, "Exit countdown cancelled!");
|
||||
app->exit_countdown_value = COUNTDOWN_START_VALUE;
|
||||
furi_event_loop_timer_stop(app->exit_timer);
|
||||
|
||||
} else {
|
||||
// The countdown is not running, print a warning message
|
||||
FURI_LOG_W(TAG, "Countdown has not been started yet");
|
||||
}
|
||||
|
||||
} else {
|
||||
// Not a Back or Ok key, just print its name.
|
||||
FURI_LOG_I(TAG, "Long press: %s", input_get_key_name(event.key));
|
||||
}
|
||||
|
||||
} else if(event.type == InputTypeShort) {
|
||||
// Not a long press, just print the key's name.
|
||||
FURI_LOG_I(TAG, "Short press: %s", input_get_key_name(event.key));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is executed each time the countdown timer expires.
|
||||
static void event_loop_multi_app_exit_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
|
||||
FURI_LOG_I(TAG, "Exiting in %lu ...", app->exit_countdown_value);
|
||||
|
||||
// If the coundown value has reached 0, exit the application
|
||||
if(app->exit_countdown_value == 0) {
|
||||
FURI_LOG_I(TAG, "Exiting NOW!");
|
||||
|
||||
// Send a signal to the worker thread to exit.
|
||||
// A signal handler that handles FuriSignalExit is already set by default.
|
||||
furi_thread_signal(app->worker_thread, FuriSignalExit, NULL);
|
||||
// Request the application event loop to stop.
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
|
||||
// Otherwise just decrement it and wait for the next time the timer expires.
|
||||
} else {
|
||||
app->exit_countdown_value -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static EventLoopMultiApp* event_loop_multi_app_alloc(void) {
|
||||
EventLoopMultiApp* app = malloc(sizeof(EventLoopMultiApp));
|
||||
// Create event loop instances.
|
||||
app->event_loop = furi_event_loop_alloc();
|
||||
|
||||
// Create a worker thread instance. The worker event loop will execute inside it.
|
||||
app->worker_thread = furi_thread_alloc_ex(
|
||||
"EventLoopMultiWorker", 1024, event_loop_multi_app_worker_thread, app);
|
||||
// Create a message queue to receive the input events.
|
||||
app->input_queue = furi_message_queue_alloc(INPUT_QUEUE_SIZE, sizeof(InputEvent));
|
||||
// Create a stream buffer to receive the generated data.
|
||||
app->stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_SIZE);
|
||||
// Create a timer to run the countdown.
|
||||
app->exit_timer = furi_event_loop_timer_alloc(
|
||||
app->event_loop,
|
||||
event_loop_multi_app_exit_timer_callback,
|
||||
FuriEventLoopTimerTypePeriodic,
|
||||
app);
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->view_port = view_port_alloc();
|
||||
// Start the countdown from this value
|
||||
app->exit_countdown_value = COUNTDOWN_START_VALUE;
|
||||
// Gain exclusive access to the input events
|
||||
view_port_input_callback_set(app->view_port, event_loop_multi_app_input_callback, app);
|
||||
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
|
||||
// Notify the event loop about incoming messages in the queue
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
app->event_loop,
|
||||
app->input_queue,
|
||||
FuriEventLoopEventIn,
|
||||
event_loop_multi_app_input_queue_callback,
|
||||
app);
|
||||
// Notify the event loop about new data in the stream buffer
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
app->event_loop,
|
||||
app->stream_buffer,
|
||||
FuriEventLoopEventIn | FuriEventLoopEventFlagEdge,
|
||||
event_loop_multi_app_stream_buffer_callback,
|
||||
app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void event_loop_multi_app_free(EventLoopMultiApp* app) {
|
||||
gui_remove_view_port(app->gui, app->view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_unsubscribe(app->event_loop, app->input_queue);
|
||||
furi_event_loop_unsubscribe(app->event_loop, app->stream_buffer);
|
||||
// Delete all instances
|
||||
view_port_free(app->view_port);
|
||||
furi_message_queue_free(app->input_queue);
|
||||
furi_stream_buffer_free(app->stream_buffer);
|
||||
// IMPORTANT: All timers MUST be deleted before deleting the associated event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_timer_free(app->exit_timer);
|
||||
furi_thread_free(app->worker_thread);
|
||||
furi_event_loop_free(app->event_loop);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void event_loop_multi_app_run(EventLoopMultiApp* app) {
|
||||
FURI_LOG_I(TAG, "Press keys to see them printed here.");
|
||||
FURI_LOG_I(TAG, "Long press \"Back\" to exit after %lu seconds.", COUNTDOWN_START_VALUE);
|
||||
FURI_LOG_I(TAG, "Long press \"Ok\" to cancel the countdown.");
|
||||
|
||||
// Start the worker thread
|
||||
furi_thread_start(app->worker_thread);
|
||||
// Run the application event loop. This call will block until the application is about to exit.
|
||||
furi_event_loop_run(app->event_loop);
|
||||
// Wait for the worker thread to finish.
|
||||
furi_thread_join(app->worker_thread);
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
* vvv START HERE vvv
|
||||
*
|
||||
* The application's entry point - referenced in application.fam
|
||||
*******************************************************************/
|
||||
int32_t example_event_loop_multi_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
EventLoopMultiApp* app = event_loop_multi_app_alloc();
|
||||
event_loop_multi_app_run(app);
|
||||
event_loop_multi_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* @file example_event_loop_mutex.c
|
||||
* @brief Example application that demonstrates the FuriEventLoop and FuriMutex integration.
|
||||
*
|
||||
* This application simulates a use case where a time-consuming blocking operation is executed
|
||||
* in a separate thread and a mutex is being used for synchronization. The application runs 10 iterations
|
||||
* of the above mentioned simulated work and prints the results to the debug output each time, then exits.
|
||||
*/
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "ExampleEventLoopMutex"
|
||||
|
||||
#define WORKER_ITERATION_COUNT (10)
|
||||
// We are interested in IN events (for the mutex, that means that the mutex has been released),
|
||||
// using edge trigger mode (reacting only to changes in mutex state) and
|
||||
// employing one-shot mode to automatically unsubscribe before the event is processed.
|
||||
#define MUTEX_EVENT_AND_FLAGS \
|
||||
(FuriEventLoopEventIn | FuriEventLoopEventFlagEdge | FuriEventLoopEventFlagOnce)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriThread* worker_thread;
|
||||
FuriMutex* worker_mutex;
|
||||
uint8_t worker_result;
|
||||
} EventLoopMutexApp;
|
||||
|
||||
// This funciton is being run in a separate thread to simulate lenghty blocking operations
|
||||
static int32_t event_loop_mutex_app_worker_thread(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMutexApp* app = context;
|
||||
|
||||
FURI_LOG_I(TAG, "Worker thread started");
|
||||
|
||||
// Run 10 iterations of simulated work
|
||||
for(uint32_t i = 0; i < WORKER_ITERATION_COUNT; ++i) {
|
||||
FURI_LOG_I(TAG, "Doing work ...");
|
||||
// Take the mutex so that no-one can access the worker_result variable
|
||||
furi_check(furi_mutex_acquire(app->worker_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
// Simulate a blocking operation with a random delay between 900 and 1100 ms
|
||||
const uint32_t work_time_ms = 900 + furi_hal_random_get() % 200;
|
||||
furi_delay_ms(work_time_ms);
|
||||
// Simulate a result with a random number between 0 and 255
|
||||
app->worker_result = furi_hal_random_get() % 0xFF;
|
||||
|
||||
FURI_LOG_I(TAG, "Work done in %lu ms", work_time_ms);
|
||||
// Release the mutex, which will notify the event loop that the result is ready
|
||||
furi_check(furi_mutex_release(app->worker_mutex) == FuriStatusOk);
|
||||
// Return control to the scheduler so that the event loop can take the mutex in its turn
|
||||
furi_thread_yield();
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "All work done, worker thread out!");
|
||||
// Request the event loop to stop
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This function is being run each time when the mutex gets released
|
||||
static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
EventLoopMutexApp* app = context;
|
||||
furi_assert(object == app->worker_mutex);
|
||||
|
||||
// Take the mutex so that no-one can access the worker_result variable
|
||||
// IMPORTANT: the wait time MUST be 0, i.e. the event loop event callbacks
|
||||
// must NOT ever block. If it is possible that the mutex will be taken by
|
||||
// others, then the event callback code must take it into account.
|
||||
furi_check(furi_mutex_acquire(app->worker_mutex, 0) == FuriStatusOk);
|
||||
// Access the worker_result variable and print it.
|
||||
FURI_LOG_I(TAG, "Result available! Value: %u", app->worker_result);
|
||||
// Release the mutex, enabling the worker thread to continue when it's ready
|
||||
furi_check(furi_mutex_release(app->worker_mutex) == FuriStatusOk);
|
||||
// Subscribe for the mutex release events again, since we were unsubscribed automatically
|
||||
// before processing the event.
|
||||
furi_event_loop_subscribe_mutex(
|
||||
app->event_loop,
|
||||
app->worker_mutex,
|
||||
MUTEX_EVENT_AND_FLAGS,
|
||||
event_loop_mutex_app_event_callback,
|
||||
app);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static EventLoopMutexApp* event_loop_mutex_app_alloc(void) {
|
||||
EventLoopMutexApp* app = malloc(sizeof(EventLoopMutexApp));
|
||||
|
||||
// Create an event loop instance.
|
||||
app->event_loop = furi_event_loop_alloc();
|
||||
// Create a worker thread instance.
|
||||
app->worker_thread = furi_thread_alloc_ex(
|
||||
"EventLoopMutexWorker", 1024, event_loop_mutex_app_worker_thread, app);
|
||||
// Create a mutex instance.
|
||||
app->worker_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
// Subscribe for the mutex release events.
|
||||
// Note that since FuriEventLoopEventFlagOneShot is used, we will be automatically unsubscribed
|
||||
// from events before entering the event processing callback. This is necessary in order to not
|
||||
// trigger on events caused by releasing the mutex in the callback.
|
||||
furi_event_loop_subscribe_mutex(
|
||||
app->event_loop,
|
||||
app->worker_mutex,
|
||||
MUTEX_EVENT_AND_FLAGS,
|
||||
event_loop_mutex_app_event_callback,
|
||||
app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void event_loop_mutex_app_free(EventLoopMutexApp* app) {
|
||||
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_unsubscribe(app->event_loop, app->worker_mutex);
|
||||
// Delete all instances
|
||||
furi_thread_free(app->worker_thread);
|
||||
furi_mutex_free(app->worker_mutex);
|
||||
furi_event_loop_free(app->event_loop);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void event_loop_mutex_app_run(EventLoopMutexApp* app) {
|
||||
furi_thread_start(app->worker_thread);
|
||||
furi_event_loop_run(app->event_loop);
|
||||
furi_thread_join(app->worker_thread);
|
||||
}
|
||||
|
||||
// The application's entry point - referenced in application.fam
|
||||
int32_t example_event_loop_mutex_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
EventLoopMutexApp* app = event_loop_mutex_app_alloc();
|
||||
event_loop_mutex_app_run(app);
|
||||
event_loop_mutex_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* @file example_event_loop_stream_buffer.c
|
||||
* @brief Example application that demonstrates the FuriEventLoop and FuriStreamBuffer integration.
|
||||
*
|
||||
* This application simulates a use case where some data data stream comes from a separate thread (or hardware)
|
||||
* and a stream buffer is used to act as an intermediate buffer. The worker thread produces 10 iterations of 32
|
||||
* bytes of simulated data, and each time when the buffer is half-filled, the data is taken out of it and printed
|
||||
* to the debug output. After completing all iterations, the application exits.
|
||||
*/
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "ExampleEventLoopStreamBuffer"
|
||||
|
||||
#define WORKER_ITERATION_COUNT (10)
|
||||
|
||||
#define STREAM_BUFFER_SIZE (32)
|
||||
#define STREAM_BUFFER_TRIG_LEVEL (STREAM_BUFFER_SIZE / 2)
|
||||
#define STREAM_BUFFER_EVENT_AND_FLAGS (FuriEventLoopEventIn | FuriEventLoopEventFlagEdge)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriThread* worker_thread;
|
||||
FuriStreamBuffer* stream_buffer;
|
||||
} EventLoopStreamBufferApp;
|
||||
|
||||
// This funciton is being run in a separate thread to simulate data coming from a producer thread or some device.
|
||||
static int32_t event_loop_stream_buffer_app_worker_thread(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopStreamBufferApp* app = context;
|
||||
|
||||
FURI_LOG_I(TAG, "Worker thread started");
|
||||
|
||||
for(uint32_t i = 0; i < WORKER_ITERATION_COUNT; ++i) {
|
||||
// Produce 32 bytes of simulated data.
|
||||
for(uint32_t j = 0; j < STREAM_BUFFER_SIZE; ++j) {
|
||||
// Simulate incoming data by generating a random byte.
|
||||
uint8_t data = furi_hal_random_get() % 0xFF;
|
||||
// Put the byte in the buffer. Depending on the use case, it may or may be not acceptable
|
||||
// to wait for free space to become available.
|
||||
furi_check(
|
||||
furi_stream_buffer_send(app->stream_buffer, &data, 1, FuriWaitForever) == 1);
|
||||
// Delay between 30 and 50 ms to slow down the output for clarity.
|
||||
furi_delay_ms(30 + furi_hal_random_get() % 20);
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "All work done, worker thread out!");
|
||||
// Request the event loop to stop
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This function is being run each time when the number of bytes in the buffer is above its trigger level.
|
||||
static bool
|
||||
event_loop_stream_buffer_app_event_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopStreamBufferApp* app = context;
|
||||
|
||||
furi_assert(object == app->stream_buffer);
|
||||
|
||||
// Temporary buffer that can hold at most half of the stream buffer's capacity.
|
||||
uint8_t data[STREAM_BUFFER_TRIG_LEVEL];
|
||||
// Receive the data. It is guaranteed that the amount of data in the buffer will be equal to
|
||||
// or greater than the trigger level, therefore, no waiting delay is necessary.
|
||||
furi_check(
|
||||
furi_stream_buffer_receive(app->stream_buffer, data, sizeof(data), 0) == sizeof(data));
|
||||
|
||||
// Format the data for printing and print it to the debug output.
|
||||
FuriString* tmp_str = furi_string_alloc();
|
||||
for(uint32_t i = 0; i < sizeof(data); ++i) {
|
||||
furi_string_cat_printf(tmp_str, "%02X ", data[i]);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
|
||||
furi_string_free(tmp_str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static EventLoopStreamBufferApp* event_loop_stream_buffer_app_alloc(void) {
|
||||
EventLoopStreamBufferApp* app = malloc(sizeof(EventLoopStreamBufferApp));
|
||||
|
||||
// Create an event loop instance.
|
||||
app->event_loop = furi_event_loop_alloc();
|
||||
// Create a worker thread instance.
|
||||
app->worker_thread = furi_thread_alloc_ex(
|
||||
"EventLoopStreamBufferWorker", 1024, event_loop_stream_buffer_app_worker_thread, app);
|
||||
// Create a stream_buffer instance.
|
||||
app->stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_TRIG_LEVEL);
|
||||
// Subscribe for the stream buffer IN events in edge triggered mode.
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
app->event_loop,
|
||||
app->stream_buffer,
|
||||
STREAM_BUFFER_EVENT_AND_FLAGS,
|
||||
event_loop_stream_buffer_app_event_callback,
|
||||
app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void event_loop_stream_buffer_app_free(EventLoopStreamBufferApp* app) {
|
||||
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_unsubscribe(app->event_loop, app->stream_buffer);
|
||||
// Delete all instances
|
||||
furi_thread_free(app->worker_thread);
|
||||
furi_stream_buffer_free(app->stream_buffer);
|
||||
furi_event_loop_free(app->event_loop);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void event_loop_stream_buffer_app_run(EventLoopStreamBufferApp* app) {
|
||||
furi_thread_start(app->worker_thread);
|
||||
furi_event_loop_run(app->event_loop);
|
||||
furi_thread_join(app->worker_thread);
|
||||
}
|
||||
|
||||
// The application's entry point - referenced in application.fam
|
||||
int32_t example_event_loop_stream_buffer_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
EventLoopStreamBufferApp* app = event_loop_stream_buffer_app_alloc();
|
||||
event_loop_stream_buffer_app_run(app);
|
||||
event_loop_stream_buffer_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @file example_event_loop_timer.c
|
||||
* @brief Example application that demonstrates FuriEventLoop's software timer capability.
|
||||
*
|
||||
* This application prints a countdown from 10 to 0 to the debug output and then exits.
|
||||
* Despite only one timer being used in this example for clarity, an event loop instance can have
|
||||
* an arbitrary number of independent timers of any type (periodic or one-shot).
|
||||
*
|
||||
*/
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "ExampleEventLoopTimer"
|
||||
|
||||
#define COUNTDOWN_START_VALUE (10)
|
||||
#define COUNTDOWN_INTERVAL_MS (1000)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriEventLoopTimer* timer;
|
||||
uint32_t countdown_value;
|
||||
} EventLoopTimerApp;
|
||||
|
||||
// This function is called each time the timer expires (i.e. once per 1000 ms (1s) in this example)
|
||||
static void event_loop_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopTimerApp* app = context;
|
||||
|
||||
// Print the countdown value
|
||||
FURI_LOG_I(TAG, "T-00:00:%02lu", app->countdown_value);
|
||||
|
||||
if(app->countdown_value == 0) {
|
||||
// If the countdown reached 0, print the final line and stop the event loop
|
||||
FURI_LOG_I(TAG, "Blast off to adventure!");
|
||||
// After this call, the control will be returned back to event_loop_timers_app_run()
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
|
||||
} else {
|
||||
// Decrement the countdown value
|
||||
app->countdown_value -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static EventLoopTimerApp* event_loop_timer_app_alloc(void) {
|
||||
EventLoopTimerApp* app = malloc(sizeof(EventLoopTimerApp));
|
||||
|
||||
// Create an event loop instance.
|
||||
app->event_loop = furi_event_loop_alloc();
|
||||
// Create a software timer instance.
|
||||
// The timer is bound to the event loop instance and will execute in its context.
|
||||
// Here, the timer type is periodic, i.e. it will restart automatically after expiring.
|
||||
app->timer = furi_event_loop_timer_alloc(
|
||||
app->event_loop, event_loop_timer_callback, FuriEventLoopTimerTypePeriodic, app);
|
||||
// The countdown value will be tracked in this variable.
|
||||
app->countdown_value = COUNTDOWN_START_VALUE;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void event_loop_timer_app_free(EventLoopTimerApp* app) {
|
||||
// IMPORTANT: All event loop timers MUST be deleted BEFORE deleting the event loop itself.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_timer_free(app->timer);
|
||||
// With all timers deleted, it's safe to delete the event loop.
|
||||
furi_event_loop_free(app->event_loop);
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void event_loop_timer_app_run(EventLoopTimerApp* app) {
|
||||
FURI_LOG_I(TAG, "All systems go! Prepare for countdown!");
|
||||
|
||||
// Timers can be started either before the event loop is run, or in any
|
||||
// callback function called by a running event loop.
|
||||
furi_event_loop_timer_start(app->timer, COUNTDOWN_INTERVAL_MS);
|
||||
// This call will block until furi_event_loop_stop() is called.
|
||||
furi_event_loop_run(app->event_loop);
|
||||
}
|
||||
|
||||
// The application's entry point - referenced in application.fam
|
||||
int32_t example_event_loop_timer_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
EventLoopTimerApp* app = event_loop_timer_app_alloc();
|
||||
event_loop_timer_app_run(app);
|
||||
event_loop_timer_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 226 B |
@@ -0,0 +1,7 @@
|
||||
# Number Input
|
||||
|
||||
Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need of intense validations after a user input.
|
||||
|
||||
Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed withing min - max, an additional button is displayed to switch the sign between + and -.
|
||||
|
||||
It is also possible to define a header text, shown in this example app with the 3 different input options.
|
||||
@@ -0,0 +1,10 @@
|
||||
App(
|
||||
appid="example_number_input",
|
||||
name="Example: Number Input",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="example_number_input",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_icon="example_number_input_10px.png",
|
||||
fap_category="Examples",
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
#include "example_number_input.h"
|
||||
|
||||
bool example_number_input_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
static bool example_number_input_back_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
return scene_manager_handle_back_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static ExampleNumberInput* example_number_input_alloc() {
|
||||
ExampleNumberInput* app = malloc(sizeof(ExampleNumberInput));
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
|
||||
app->scene_manager = scene_manager_alloc(&example_number_input_scene_handlers, app);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, example_number_input_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, example_number_input_back_event_callback);
|
||||
|
||||
app->number_input = number_input_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
ExampleNumberInputViewIdNumberInput,
|
||||
number_input_get_view(app->number_input));
|
||||
|
||||
app->dialog_ex = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
ExampleNumberInputViewIdShowNumber,
|
||||
dialog_ex_get_view(app->dialog_ex));
|
||||
|
||||
app->current_number = 5;
|
||||
app->min_value = INT32_MIN;
|
||||
app->max_value = INT32_MAX;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void example_number_input_free(ExampleNumberInput* app) {
|
||||
furi_assert(app);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ExampleNumberInputViewIdShowNumber);
|
||||
dialog_ex_free(app->dialog_ex);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||
number_input_free(app->number_input);
|
||||
|
||||
scene_manager_free(app->scene_manager);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
app->gui = NULL;
|
||||
|
||||
//Remove whatever is left
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t example_number_input(void* p) {
|
||||
UNUSED(p);
|
||||
ExampleNumberInput* app = example_number_input_alloc();
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneShowNumber);
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
example_number_input_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/number_input.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <input/input.h>
|
||||
|
||||
#include "scenes/example_number_input_scene.h"
|
||||
|
||||
typedef struct ExampleNumberInputShowNumber ExampleNumberInputShowNumber;
|
||||
|
||||
typedef enum {
|
||||
ExampleNumberInputViewIdShowNumber,
|
||||
ExampleNumberInputViewIdNumberInput,
|
||||
} ExampleNumberInputViewId;
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
SceneManager* scene_manager;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
|
||||
NumberInput* number_input;
|
||||
DialogEx* dialog_ex;
|
||||
|
||||
int32_t current_number;
|
||||
int32_t min_value;
|
||||
int32_t max_value;
|
||||
} ExampleNumberInput;
|
||||
|
After Width: | Height: | Size: 87 B |
@@ -0,0 +1,30 @@
|
||||
#include "example_number_input_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const example_number_input_on_enter_handlers[])(void*) = {
|
||||
#include "example_number_input_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const example_number_input_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "example_number_input_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const example_number_input_on_exit_handlers[])(void* context) = {
|
||||
#include "example_number_input_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers example_number_input_scene_handlers = {
|
||||
.on_enter_handlers = example_number_input_on_enter_handlers,
|
||||
.on_event_handlers = example_number_input_on_event_handlers,
|
||||
.on_exit_handlers = example_number_input_on_exit_handlers,
|
||||
.scene_num = ExampleNumberInputSceneNum,
|
||||
};
|
||||
@@ -3,27 +3,27 @@
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) StorageMoveToSd##id,
|
||||
#define ADD_SCENE(prefix, name, id) ExampleNumberInputScene##id,
|
||||
typedef enum {
|
||||
#include "storage_move_to_sd_scene_config.h"
|
||||
StorageMoveToSdSceneNum,
|
||||
} StorageMoveToSdScene;
|
||||
#include "example_number_input_scene_config.h"
|
||||
ExampleNumberInputSceneNum,
|
||||
} ExampleNumberInputScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers storage_move_to_sd_scene_handlers;
|
||||
extern const SceneManagerHandlers example_number_input_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "storage_move_to_sd_scene_config.h"
|
||||
#include "example_number_input_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||
#include "storage_move_to_sd_scene_config.h"
|
||||
#include "example_number_input_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "storage_move_to_sd_scene_config.h"
|
||||
#include "example_number_input_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@@ -0,0 +1,4 @@
|
||||
ADD_SCENE(example_number_input, input_number, InputNumber)
|
||||
ADD_SCENE(example_number_input, show_number, ShowNumber)
|
||||
ADD_SCENE(example_number_input, input_max, InputMax)
|
||||
ADD_SCENE(example_number_input, input_min, InputMin)
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "../example_number_input.h"
|
||||
|
||||
void example_number_input_scene_input_max_callback(void* context, int32_t number) {
|
||||
ExampleNumberInput* app = context;
|
||||
app->max_value = number;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_max_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
NumberInput* number_input = app->number_input;
|
||||
|
||||
number_input_set_header_text(number_input, "Enter the maximum value");
|
||||
number_input_set_result_callback(
|
||||
number_input,
|
||||
example_number_input_scene_input_max_callback,
|
||||
context,
|
||||
app->max_value,
|
||||
app->min_value,
|
||||
INT32_MAX);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||
}
|
||||
|
||||
bool example_number_input_scene_input_max_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleNumberInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
return true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_max_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "../example_number_input.h"
|
||||
|
||||
void example_number_input_scene_input_min_callback(void* context, int32_t number) {
|
||||
ExampleNumberInput* app = context;
|
||||
app->min_value = number;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_min_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
NumberInput* number_input = app->number_input;
|
||||
|
||||
number_input_set_header_text(number_input, "Enter the minimum value");
|
||||
number_input_set_result_callback(
|
||||
number_input,
|
||||
example_number_input_scene_input_min_callback,
|
||||
context,
|
||||
app->min_value,
|
||||
INT32_MIN,
|
||||
app->max_value);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||
}
|
||||
|
||||
bool example_number_input_scene_input_min_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleNumberInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
return true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_min_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "../example_number_input.h"
|
||||
|
||||
void example_number_input_scene_input_number_callback(void* context, int32_t number) {
|
||||
ExampleNumberInput* app = context;
|
||||
app->current_number = number;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_number_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
NumberInput* number_input = app->number_input;
|
||||
|
||||
char str[50];
|
||||
snprintf(str, sizeof(str), "Set Number (%ld - %ld)", app->min_value, app->max_value);
|
||||
|
||||
number_input_set_header_text(number_input, str);
|
||||
number_input_set_result_callback(
|
||||
number_input,
|
||||
example_number_input_scene_input_number_callback,
|
||||
context,
|
||||
app->current_number,
|
||||
app->min_value,
|
||||
app->max_value);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||
}
|
||||
|
||||
bool example_number_input_scene_input_number_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleNumberInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) { //Back button pressed
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
return true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_number_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "../example_number_input.h"
|
||||
|
||||
static void
|
||||
example_number_input_scene_confirm_dialog_callback(DialogExResult result, void* context) {
|
||||
ExampleNumberInput* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
static void example_number_input_scene_update_view(void* context) {
|
||||
ExampleNumberInput* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "The number is", 64, 0, AlignCenter, AlignTop);
|
||||
|
||||
static char buffer[12]; //needs static for extended lifetime
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "%ld", app->current_number);
|
||||
dialog_ex_set_text(dialog_ex, buffer, 64, 29, AlignCenter, AlignCenter);
|
||||
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Min");
|
||||
dialog_ex_set_right_button_text(dialog_ex, "Max");
|
||||
dialog_ex_set_center_button_text(dialog_ex, "Change");
|
||||
|
||||
dialog_ex_set_result_callback(dialog_ex, example_number_input_scene_confirm_dialog_callback);
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
}
|
||||
|
||||
void example_number_input_scene_show_number_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
|
||||
example_number_input_scene_update_view(app);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdShowNumber);
|
||||
}
|
||||
|
||||
bool example_number_input_scene_show_number_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleNumberInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultCenter:
|
||||
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputNumber);
|
||||
consumed = true;
|
||||
break;
|
||||
case DialogExResultLeft:
|
||||
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputMin);
|
||||
consumed = true;
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputMax);
|
||||
consumed = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_number_input_scene_show_number_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 91 B |
@@ -0,0 +1,8 @@
|
||||
App(
|
||||
appid="example_view_dispatcher",
|
||||
name="Example: ViewDispatcher",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="example_view_dispatcher_app",
|
||||
requires=["gui"],
|
||||
fap_category="Examples",
|
||||
)
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* @file example_view_dispatcher.c
|
||||
* @brief Example application demonstrating the usage of the ViewDispatcher library.
|
||||
*
|
||||
* This application can display one of two views: either a Widget or a Submenu.
|
||||
* Each view has its own way of switching to another one:
|
||||
*
|
||||
* - A center button in the Widget view.
|
||||
* - A submenu item in the Submenu view
|
||||
*
|
||||
* Press either to switch to a different view. Press Back to exit the application.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
|
||||
// Enumeration of the view indexes.
|
||||
typedef enum {
|
||||
ViewIndexWidget,
|
||||
ViewIndexSubmenu,
|
||||
ViewIndexCount,
|
||||
} ViewIndex;
|
||||
|
||||
// Enumeration of submenu items.
|
||||
typedef enum {
|
||||
SubmenuIndexNothing,
|
||||
SubmenuIndexSwitchView,
|
||||
} SubmenuIndex;
|
||||
|
||||
// Main application structure.
|
||||
typedef struct {
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Widget* widget;
|
||||
Submenu* submenu;
|
||||
} ExampleViewDispatcherApp;
|
||||
|
||||
// This function is called when the user has pressed the Back key.
|
||||
static bool example_view_dispatcher_app_navigation_callback(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleViewDispatcherApp* app = context;
|
||||
// Back means exit the application, which can be done by stopping the ViewDispatcher.
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is called when there are custom events to process.
|
||||
static bool example_view_dispatcher_app_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
ExampleViewDispatcherApp* app = context;
|
||||
// The event numerical value can mean different things (the application is responsible to uphold its chosen convention)
|
||||
// In this example, the only possible meaning is the view index to switch to.
|
||||
furi_assert(event < ViewIndexCount);
|
||||
// Switch to the requested view.
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is called when the user presses the "Switch View" button on the Widget view.
|
||||
static void example_view_dispatcher_app_button_callback(
|
||||
GuiButtonType button_type,
|
||||
InputType input_type,
|
||||
void* context) {
|
||||
furi_assert(context);
|
||||
ExampleViewDispatcherApp* app = context;
|
||||
// Only request the view switch if the user short-presses the Center button.
|
||||
if(button_type == GuiButtonTypeCenter && input_type == InputTypeShort) {
|
||||
// Request switch to the Submenu view via the custom event queue.
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, ViewIndexSubmenu);
|
||||
}
|
||||
}
|
||||
|
||||
// This function is called when the user activates the "Switch View" submenu item.
|
||||
static void example_view_dispatcher_app_submenu_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
ExampleViewDispatcherApp* app = context;
|
||||
// Only request the view switch if the user activates the "Switch View" item.
|
||||
if(index == SubmenuIndexSwitchView) {
|
||||
// Request switch to the Widget view via the custom event queue.
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, ViewIndexWidget);
|
||||
}
|
||||
}
|
||||
|
||||
// Application constructor function.
|
||||
static ExampleViewDispatcherApp* example_view_dispatcher_app_alloc() {
|
||||
ExampleViewDispatcherApp* app = malloc(sizeof(ExampleViewDispatcherApp));
|
||||
// Access the GUI API instance.
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
// Create and initialize the Widget view.
|
||||
app->widget = widget_alloc();
|
||||
widget_add_string_multiline_element(
|
||||
app->widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, "Press the Button below");
|
||||
widget_add_button_element(
|
||||
app->widget,
|
||||
GuiButtonTypeCenter,
|
||||
"Switch View",
|
||||
example_view_dispatcher_app_button_callback,
|
||||
app);
|
||||
// Create and initialize the Submenu view.
|
||||
app->submenu = submenu_alloc();
|
||||
submenu_add_item(app->submenu, "Do Nothing", SubmenuIndexNothing, NULL, NULL);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Switch View",
|
||||
SubmenuIndexSwitchView,
|
||||
example_view_dispatcher_app_submenu_callback,
|
||||
app);
|
||||
// Create the ViewDispatcher instance.
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
// Let the GUI know about this ViewDispatcher instance.
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
||||
// Register the views within the ViewDispatcher instance. This alone will not show any of them on the screen.
|
||||
// Each view must have its own index to refer to it later (it is best done via an enumeration as shown here).
|
||||
view_dispatcher_add_view(app->view_dispatcher, ViewIndexWidget, widget_get_view(app->widget));
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, ViewIndexSubmenu, submenu_get_view(app->submenu));
|
||||
// Set the custom event callback. It will be called each time a custom event is scheduled
|
||||
// using the view_dispatcher_send_custom_callback() function.
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, example_view_dispatcher_app_custom_event_callback);
|
||||
// Set the navigation, or back button callback. It will be called if the user pressed the Back button
|
||||
// and the event was not handled in the currently displayed view.
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, example_view_dispatcher_app_navigation_callback);
|
||||
// The context will be passed to the callbacks as a parameter, so we have access to our application object.
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
// Application destructor function.
|
||||
static void example_view_dispatcher_app_free(ExampleViewDispatcherApp* app) {
|
||||
// All views must be un-registered (removed) from a ViewDispatcher instance
|
||||
// before deleting it. Failure to do so will result in a crash.
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ViewIndexWidget);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ViewIndexSubmenu);
|
||||
// Now it is safe to delete the ViewDispatcher instance.
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
// Delete the views
|
||||
widget_free(app->widget);
|
||||
submenu_free(app->submenu);
|
||||
// End access to hte the GUI API.
|
||||
furi_record_close(RECORD_GUI);
|
||||
// Free the remaining memory.
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void example_view_dispatcher_app_run(ExampleViewDispatcherApp* app) {
|
||||
// Display the Widget view on the screen.
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ViewIndexWidget);
|
||||
// This function will block until view_dispatcher_stop() is called.
|
||||
// Internally, it uses a FuriEventLoop (see FuriEventLoop examples for more info on this).
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
* vvv START HERE vvv
|
||||
*
|
||||
* The application's entry point - referenced in application.fam
|
||||
*******************************************************************/
|
||||
int32_t example_view_dispatcher_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
ExampleViewDispatcherApp* app = example_view_dispatcher_app_alloc();
|
||||
example_view_dispatcher_app_run(app);
|
||||
example_view_dispatcher_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
App(
|
||||
appid="example_view_holder",
|
||||
name="Example: ViewHolder",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="example_view_holder_app",
|
||||
requires=["gui"],
|
||||
fap_category="Examples",
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @file example_view_holder.c
|
||||
* @brief Example application demonstrating the usage of the ViewHolder library.
|
||||
*
|
||||
* This application will display a text box with some scrollable text in it.
|
||||
* Press the Back key to exit the application.
|
||||
*/
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_holder.h>
|
||||
#include <gui/modules/text_box.h>
|
||||
|
||||
#include <api_lock.h>
|
||||
|
||||
// This function will be called when the user presses the Back button.
|
||||
static void example_view_holder_back_callback(void* context) {
|
||||
FuriApiLock exit_lock = context;
|
||||
// Unlock the exit lock, thus enabling the app to exit.
|
||||
api_lock_unlock(exit_lock);
|
||||
}
|
||||
|
||||
int32_t example_view_holder_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
// Access the GUI API instance.
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
// Create a TextBox view. The Gui object only accepts
|
||||
// ViewPort instances, so we will need to address that later.
|
||||
TextBox* text_box = text_box_alloc();
|
||||
// Set some text so that the text box is not empty.
|
||||
text_box_set_text(
|
||||
text_box,
|
||||
"ViewHolder is being used\n"
|
||||
"to show this TextBox view.\n\n"
|
||||
"Scroll down to see more.\n\n\n"
|
||||
"Press \"Back\" to exit.");
|
||||
|
||||
// Create a ViewHolder instance. It will serve as an adapter to convert
|
||||
// between the View type provided by the TextBox view and the ViewPort type
|
||||
// that the GUI can actually display.
|
||||
ViewHolder* view_holder = view_holder_alloc();
|
||||
// Let the GUI know about this ViewHolder instance.
|
||||
view_holder_attach_to_gui(view_holder, gui);
|
||||
// Set the view that we want to display.
|
||||
view_holder_set_view(view_holder, text_box_get_view(text_box));
|
||||
|
||||
// The part below is not really related to this example, but is necessary for it to function.
|
||||
// We need to somehow stall the application thread so that the view stays on the screen (otherwise
|
||||
// the app will just exit and won't display anything) and at the same time we need a way to quit out
|
||||
// of the application.
|
||||
|
||||
// In this example, a simple FuriApiLock instance is used. A real-world application is likely to have some
|
||||
// kind of event handling loop here instead. (see the ViewDispatcher example or one of FuriEventLoop
|
||||
// examples for that).
|
||||
|
||||
// Create a pre-locked FuriApiLock instance.
|
||||
FuriApiLock exit_lock = api_lock_alloc_locked();
|
||||
// Set a Back event callback for the ViewHolder instance. It will be called when the user
|
||||
// presses the Back button. We pass the exit lock instance as the context to be able to access
|
||||
// it inside the callback function.
|
||||
view_holder_set_back_callback(view_holder, example_view_holder_back_callback, exit_lock);
|
||||
|
||||
// This call will block the application thread from running until the exit lock gets unlocked somehow
|
||||
// (the only way it can happen in this example is via the back callback).
|
||||
api_lock_wait_unlock_and_free(exit_lock);
|
||||
|
||||
// The back key has been pressed, which unlocked the exit lock. The application is about to exit.
|
||||
|
||||
// The view must be removed from a ViewHolder instance before deleting it.
|
||||
view_holder_set_view(view_holder, NULL);
|
||||
// Delete everything to prevent memory leaks.
|
||||
view_holder_free(view_holder);
|
||||
text_box_free(text_box);
|
||||
// End access to the GUI API.
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -30,7 +30,6 @@ static ArchiveApp* archive_alloc(void) {
|
||||
archive->view_dispatcher = view_dispatcher_alloc();
|
||||
|
||||
ViewDispatcher* view_dispatcher = archive->view_dispatcher;
|
||||
view_dispatcher_enable_queue(view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(view_dispatcher, archive);
|
||||
view_dispatcher_set_custom_event_callback(view_dispatcher, archive_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(view_dispatcher, archive_back_event_callback);
|
||||
|
||||
@@ -510,18 +510,15 @@ void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active) {
|
||||
}
|
||||
|
||||
static bool archive_is_dir_exists(FuriString* path) {
|
||||
if(furi_string_equal(path, STORAGE_INT_PATH_PREFIX) ||
|
||||
furi_string_equal(path, STORAGE_EXT_PATH_PREFIX) ||
|
||||
furi_string_equal(path, STORAGE_MNT_PATH_PREFIX)) {
|
||||
return true;
|
||||
}
|
||||
bool state = false;
|
||||
FileInfo file_info;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) {
|
||||
if(file_info_is_dir(&file_info)) {
|
||||
state = true;
|
||||
}
|
||||
|
||||
if(furi_string_equal(path, STORAGE_EXT_PATH_PREFIX) ||
|
||||
furi_string_equal(path, STORAGE_MNT_PATH_PREFIX)) {
|
||||
state = storage_sd_status(storage) == FSE_OK;
|
||||
} else if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) {
|
||||
state = file_info_is_dir(&file_info);
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return state;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define ARCHIVE_FAV_PATH ANY_PATH("favorites.txt")
|
||||
#define ARCHIVE_FAV_TEMP_PATH ANY_PATH("favorites.tmp")
|
||||
#define ARCHIVE_FAV_PATH EXT_PATH("favorites.txt")
|
||||
#define ARCHIVE_FAV_TEMP_PATH EXT_PATH("favorites.tmp")
|
||||
|
||||
uint16_t archive_favorites_count(void);
|
||||
bool archive_favorites_read(void* context);
|
||||
|
||||
@@ -18,7 +18,7 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder
|
||||
} else {
|
||||
for(size_t i = 0; i < COUNT_OF(known_ext); i++) {
|
||||
if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue;
|
||||
if(furi_string_end_with(file->path, known_ext[i])) {
|
||||
if(furi_string_end_withi(file->path, known_ext[i])) {
|
||||
// Check for .txt containing folder
|
||||
if(strcmp(known_ext[i], ".txt") == 0) {
|
||||
const char* txt_path = NULL;
|
||||
|
||||
@@ -320,7 +320,6 @@ BadKbApp* bad_kb_app_alloc(char* arg) {
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
app->scene_manager = scene_manager_alloc(&bad_kb_scene_handlers, app);
|
||||
|
||||
|
||||
@@ -373,6 +373,12 @@ bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta)
|
||||
return state;
|
||||
}
|
||||
|
||||
// AN5289: 4.7, in order to use flash controller interval must be at least 25ms + advertisement, which is 30 ms
|
||||
// Since we don't use flash controller anymore interval can be lowered to 7.5ms
|
||||
#define CONNECTION_INTERVAL_MIN (0x0006)
|
||||
// Up to 45 ms
|
||||
#define CONNECTION_INTERVAL_MAX (0x24)
|
||||
|
||||
static GapConfig template_config = {
|
||||
.adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
|
||||
.appearance_char = GAP_APPEARANCE_KEYBOARD,
|
||||
@@ -380,8 +386,8 @@ static GapConfig template_config = {
|
||||
.pairing_method = GapPairingPinCodeVerifyYesNo,
|
||||
.conn_param =
|
||||
{
|
||||
.conn_int_min = 0x18, // 30 ms
|
||||
.conn_int_max = 0x24, // 45 ms
|
||||
.conn_int_min = CONNECTION_INTERVAL_MIN,
|
||||
.conn_int_max = CONNECTION_INTERVAL_MAX,
|
||||
.slave_latency = 0,
|
||||
.supervisor_timeout = 0,
|
||||
},
|
||||
|
||||
|
Before Width: | Height: | Size: 576 B After Width: | Height: | Size: 96 B |
@@ -32,7 +32,6 @@ GpioApp* gpio_app_alloc(void) {
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 91 B |
@@ -87,7 +87,6 @@ iButton* ibutton_alloc(void) {
|
||||
ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton);
|
||||
|
||||
ibutton->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(ibutton->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(ibutton->view_dispatcher, ibutton);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
ibutton->view_dispatcher, ibutton_custom_event_callback);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include "ibutton_custom_event.h"
|
||||
#include "scenes/ibutton_scene.h"
|
||||
|
||||
#define IBUTTON_APP_FOLDER ANY_PATH("ibutton")
|
||||
#define IBUTTON_APP_FOLDER EXT_PATH("ibutton")
|
||||
#define IBUTTON_APP_FILENAME_PREFIX "iBtn"
|
||||
#define IBUTTON_APP_FILENAME_EXTENSION ".ibtn"
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 305 B After Width: | Height: | Size: 96 B |
@@ -150,7 +150,6 @@ static InfraredApp* infrared_alloc(void) {
|
||||
infrared->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
|
||||
view_dispatcher_enable_queue(view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(view_dispatcher, infrared);
|
||||
view_dispatcher_set_custom_event_callback(view_dispatcher, infrared_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(view_dispatcher, infrared_back_event_callback);
|
||||
@@ -476,6 +475,7 @@ static void infrared_load_settings(InfraredApp* infrared) {
|
||||
INFRARED_SETTINGS_MAGIC,
|
||||
INFRARED_SETTINGS_VERSION)) {
|
||||
FURI_LOG_D(TAG, "Failed to load settings, using defaults");
|
||||
// infrared_save_settings(infrared);
|
||||
}
|
||||
|
||||
infrared_set_tx_pin(infrared, settings.tx_pin);
|
||||
|
||||
@@ -17,7 +17,7 @@ typedef struct InfraredApp InfraredApp;
|
||||
#include <storage/storage.h>
|
||||
#include <furi_hal_infrared.h>
|
||||
|
||||
#define INFRARED_SETTINGS_PATH EXT_PATH("infrared/.infrared.settings")
|
||||
#define INFRARED_SETTINGS_PATH INT_PATH(".infrared.settings")
|
||||
#define INFRARED_SETTINGS_VERSION (1)
|
||||
#define INFRARED_SETTINGS_MAGIC (0x1F)
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
#define INFRARED_MAX_BUTTON_NAME_LENGTH 22
|
||||
#define INFRARED_MAX_REMOTE_NAME_LENGTH 22
|
||||
|
||||
#define INFRARED_APP_FOLDER ANY_PATH("infrared")
|
||||
#define INFRARED_APP_FOLDER EXT_PATH("infrared")
|
||||
#define INFRARED_APP_EXTENSION ".ir"
|
||||
|
||||
#define INFRARED_DEFAULT_REMOTE_NAME "Remote"
|
||||
|
||||
|
Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 95 B |
@@ -78,7 +78,6 @@ static LfRfid* lfrfid_alloc(void) {
|
||||
|
||||
lfrfid->view_dispatcher = view_dispatcher_alloc();
|
||||
lfrfid->scene_manager = scene_manager_alloc(&lfrfid_scene_handlers, lfrfid);
|
||||
view_dispatcher_enable_queue(lfrfid->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(lfrfid->view_dispatcher, lfrfid);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
lfrfid->view_dispatcher, lfrfid_debug_custom_event_callback);
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
#define LFRFID_TEXT_STORE_SIZE 40
|
||||
|
||||
#define LFRFID_APP_FOLDER ANY_PATH("lfrfid")
|
||||
#define LFRFID_APP_FOLDER EXT_PATH("lfrfid")
|
||||
#define LFRFID_SD_FOLDER EXT_PATH("lfrfid")
|
||||
#define LFRFID_APP_FILENAME_PREFIX "RFID"
|
||||
#define LFRFID_APP_FILENAME_EXTENSION ".rfid"
|
||||
|
||||
|
Before Width: | Height: | Size: 620 B After Width: | Height: | Size: 95 B |
@@ -0,0 +1,7 @@
|
||||
// Since settings app is external, it cannot access firmware functions
|
||||
// For simple utils like this, easier to include C code in app rather than exposing to API
|
||||
// Instead of copying the file, can (ab)use the preprocessor to insert the source code here
|
||||
// Then, we still use the Header from original code as if nothing happened
|
||||
|
||||
// desktop_api_get_settings(), desktop_api_set_settings()
|
||||
#include <applications/services/desktop/desktop.c>
|
||||
@@ -0,0 +1,7 @@
|
||||
// Since settings app is external, it cannot access firmware functions
|
||||
// For simple utils like this, easier to include C code in app rather than exposing to API
|
||||
// Instead of copying the file, can (ab)use the preprocessor to insert the source code here
|
||||
// Then, we still use the Header from original code as if nothing happened
|
||||
|
||||
// DOLPHIN_LEVELS, DOLPHIN_LEVEL_COUNT
|
||||
#include <applications/services/dolphin/helpers/dolphin_state.c>
|
||||
@@ -8,7 +8,8 @@ static bool momentum_app_custom_event_callback(void* context, uint32_t event) {
|
||||
|
||||
void callback_reboot(void* context) {
|
||||
UNUSED(context);
|
||||
power_reboot(PowerBootModeNormal);
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
power_reboot(power, PowerBootModeNormal);
|
||||
}
|
||||
|
||||
bool momentum_app_apply(MomentumApp* app) {
|
||||
@@ -26,6 +27,10 @@ bool momentum_app_apply(MomentumApp* app) {
|
||||
stream_free(stream);
|
||||
}
|
||||
|
||||
if(app->save_desktop) {
|
||||
desktop_api_set_settings(app->desktop, &app->desktop_settings);
|
||||
}
|
||||
|
||||
if(app->save_subghz_freqs) {
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
do {
|
||||
@@ -97,7 +102,7 @@ bool momentum_app_apply(MomentumApp* app) {
|
||||
}
|
||||
}
|
||||
|
||||
if(app->save_level || app->save_angry) {
|
||||
if(app->save_dolphin) {
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
if(app->save_level) {
|
||||
int32_t xp = app->dolphin_level > 1 ? DOLPHIN_LEVELS[app->dolphin_level - 2] : 0;
|
||||
@@ -107,7 +112,8 @@ bool momentum_app_apply(MomentumApp* app) {
|
||||
dolphin->state->data.butthurt = app->dolphin_angry;
|
||||
}
|
||||
dolphin->state->dirty = true;
|
||||
dolphin_state_save(dolphin->state);
|
||||
dolphin_flush(dolphin);
|
||||
dolphin_reload_state(dolphin);
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
}
|
||||
|
||||
@@ -180,6 +186,7 @@ static void momentum_app_push_mainmenu_app(MomentumApp* app, FuriString* label,
|
||||
MomentumApp* momentum_app_alloc() {
|
||||
MomentumApp* app = malloc(sizeof(MomentumApp));
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->desktop = furi_record_open(RECORD_DESKTOP);
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
app->expansion = furi_record_open(RECORD_EXPANSION);
|
||||
app->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
@@ -187,7 +194,7 @@ MomentumApp* momentum_app_alloc() {
|
||||
// View Dispatcher and Scene Manager
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&momentum_app_scene_handlers, app);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
@@ -216,6 +223,12 @@ MomentumApp* momentum_app_alloc() {
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, MomentumAppViewByteInput, byte_input_get_view(app->byte_input));
|
||||
|
||||
app->number_input = number_input_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
MomentumAppViewNumberInput,
|
||||
number_input_get_view(app->number_input));
|
||||
|
||||
app->popup = popup_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, MomentumAppViewPopup, popup_get_view(app->popup));
|
||||
@@ -275,6 +288,8 @@ MomentumApp* momentum_app_alloc() {
|
||||
furi_string_set(line, "125 kHz RFID");
|
||||
} else if(furi_string_equal(line, "SubGHz")) {
|
||||
furi_string_set(line, "Sub-GHz");
|
||||
} else if(furi_string_equal(line, "Xtreme")) {
|
||||
furi_string_set(line, "Momentum");
|
||||
}
|
||||
}
|
||||
if(furi_string_start_with(line, "/")) {
|
||||
@@ -322,6 +337,8 @@ MomentumApp* momentum_app_alloc() {
|
||||
file_stream_close(stream);
|
||||
stream_free(stream);
|
||||
|
||||
desktop_api_get_settings(app->desktop, &app->desktop_settings);
|
||||
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
FrequencyList_init(app->subghz_static_freqs);
|
||||
FrequencyList_init(app->subghz_hopper_freqs);
|
||||
@@ -410,6 +427,8 @@ void momentum_app_free(MomentumApp* app) {
|
||||
text_input_free(app->text_input);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewByteInput);
|
||||
byte_input_free(app->byte_input);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewNumberInput);
|
||||
number_input_free(app->number_input);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewPopup);
|
||||
popup_free(app->popup);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewDialogEx);
|
||||
@@ -445,6 +464,7 @@ void momentum_app_free(MomentumApp* app) {
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_EXPANSION);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
furi_record_close(RECORD_DESKTOP);
|
||||
furi_record_close(RECORD_GUI);
|
||||
free(app);
|
||||
}
|
||||
|
||||
@@ -1,51 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <desktop/desktop.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <expansion/expansion.h>
|
||||
#include <notification/notification_app.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <power/power_service/power.h>
|
||||
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/modules/byte_input.h>
|
||||
#include <gui/modules/number_input.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
|
||||
#include <momentum/asset_packs.h>
|
||||
#include <loader/loader_menu.h>
|
||||
#include <lib/subghz/subghz_setting.h>
|
||||
#include <rgb_backlight.h>
|
||||
#include <momentum/namespoof.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <dolphin/dolphin_i.h>
|
||||
#include <dolphin/helpers/dolphin_state.h>
|
||||
#include <momentum/settings.h>
|
||||
|
||||
#include <applications.h>
|
||||
#include <assets_icons.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <dolphin/dolphin_i.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <dolphin/helpers/dolphin_state.h>
|
||||
#include <expansion/expansion.h>
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/modules/byte_input.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/view.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
#include <lib/subghz/subghz_setting.h>
|
||||
#include <lib/toolbox/value_index.h>
|
||||
#include <loader/loader_menu.h>
|
||||
#include <m-array.h>
|
||||
#include <momentum/asset_packs.h>
|
||||
#include <momentum/namespoof.h>
|
||||
#include <momentum/settings.h>
|
||||
#include <notification/notification_app.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <rgb_backlight.h>
|
||||
#include <scenes/momentum_app_scene.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
|
||||
#include "scenes/momentum_app_scene.h"
|
||||
|
||||
ARRAY_DEF(CharList, char*)
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
Desktop* desktop;
|
||||
DialogsApp* dialogs;
|
||||
Expansion* expansion;
|
||||
NotificationApp* notification;
|
||||
SceneManager* scene_manager;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
|
||||
VariableItemList* var_item_list;
|
||||
Submenu* submenu;
|
||||
TextInput* text_input;
|
||||
ByteInput* byte_input;
|
||||
NumberInput* number_input;
|
||||
Popup* popup;
|
||||
DialogEx* dialog_ex;
|
||||
|
||||
@@ -54,12 +63,12 @@ typedef struct {
|
||||
CharList_t mainmenu_app_labels;
|
||||
CharList_t mainmenu_app_exes;
|
||||
uint8_t mainmenu_app_index;
|
||||
DesktopSettings desktop_settings;
|
||||
bool subghz_use_defaults;
|
||||
FrequencyList_t subghz_static_freqs;
|
||||
uint8_t subghz_static_index;
|
||||
FrequencyList_t subghz_hopper_freqs;
|
||||
uint8_t subghz_hopper_index;
|
||||
char subghz_freq_buffer[7];
|
||||
bool subghz_extend;
|
||||
bool subghz_bypass;
|
||||
RgbColor lcd_color;
|
||||
@@ -70,11 +79,13 @@ typedef struct {
|
||||
FuriString* version_tag;
|
||||
|
||||
bool save_mainmenu_apps;
|
||||
bool save_desktop;
|
||||
bool save_subghz_freqs;
|
||||
bool save_subghz;
|
||||
bool save_name;
|
||||
bool save_level;
|
||||
bool save_angry;
|
||||
bool save_dolphin;
|
||||
bool save_backlight;
|
||||
bool save_settings;
|
||||
bool apply_pack;
|
||||
@@ -87,6 +98,7 @@ typedef enum {
|
||||
MomentumAppViewSubmenu,
|
||||
MomentumAppViewTextInput,
|
||||
MomentumAppViewByteInput,
|
||||
MomentumAppViewNumberInput,
|
||||
MomentumAppViewPopup,
|
||||
MomentumAppViewDialogEx,
|
||||
} MomentumAppView;
|
||||
|
||||
@@ -28,16 +28,14 @@ static void momentum_app_scene_interface_statusbar_battery_icon_changed(Variable
|
||||
variable_item_set_current_value_text(item, battery_icon_names[index]);
|
||||
momentum_settings.battery_icon = index;
|
||||
app->save_settings = true;
|
||||
power_set_battery_icon_enabled(furi_record_open(RECORD_POWER), index != BatteryIconOff);
|
||||
furi_record_close(RECORD_POWER);
|
||||
}
|
||||
|
||||
static void momentum_app_scene_interface_statusbar_statusbar_clock_changed(VariableItem* item) {
|
||||
MomentumApp* app = variable_item_get_context(item);
|
||||
bool value = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, value ? "ON" : "OFF");
|
||||
momentum_settings.statusbar_clock = value;
|
||||
app->save_settings = true;
|
||||
app->desktop_settings.display_clock = value;
|
||||
app->save_desktop = true;
|
||||
}
|
||||
|
||||
static void momentum_app_scene_interface_statusbar_status_icons_changed(VariableItem* item) {
|
||||
@@ -84,8 +82,8 @@ void momentum_app_scene_interface_statusbar_on_enter(void* context) {
|
||||
2,
|
||||
momentum_app_scene_interface_statusbar_statusbar_clock_changed,
|
||||
app);
|
||||
variable_item_set_current_value_index(item, momentum_settings.statusbar_clock);
|
||||
variable_item_set_current_value_text(item, momentum_settings.statusbar_clock ? "ON" : "OFF");
|
||||
variable_item_set_current_value_index(item, app->desktop_settings.display_clock);
|
||||
variable_item_set_current_value_text(item, app->desktop_settings.display_clock ? "ON" : "OFF");
|
||||
|
||||
item = variable_item_list_add(
|
||||
var_item_list,
|
||||
|
||||
@@ -18,6 +18,7 @@ static void momentum_app_scene_misc_dolphin_dolphin_level_changed(VariableItem*
|
||||
snprintf(level_str, 4, "%li", app->dolphin_level);
|
||||
variable_item_set_current_value_text(item, level_str);
|
||||
app->save_level = true;
|
||||
app->save_dolphin = true;
|
||||
}
|
||||
|
||||
static void momentum_app_scene_misc_dolphin_dolphin_angry_changed(VariableItem* item) {
|
||||
@@ -27,6 +28,7 @@ static void momentum_app_scene_misc_dolphin_dolphin_angry_changed(VariableItem*
|
||||
snprintf(angry_str, 4, "%li", app->dolphin_angry);
|
||||
variable_item_set_current_value_text(item, angry_str);
|
||||
app->save_angry = true;
|
||||
app->save_dolphin = true;
|
||||
}
|
||||
|
||||
const char* const butthurt_timer_names[] = {
|
||||
@@ -59,7 +61,7 @@ static void momentum_app_scene_misc_dolphin_butthurt_timer_changed(VariableItem*
|
||||
variable_item_set_current_value_text(item, butthurt_timer_names[index]);
|
||||
momentum_settings.butthurt_timer = butthurt_timer_values[index];
|
||||
app->save_settings = true;
|
||||
app->require_reboot = true;
|
||||
app->save_dolphin = true;
|
||||
}
|
||||
|
||||
void momentum_app_scene_misc_dolphin_on_enter(void* context) {
|
||||
|
||||
@@ -5,12 +5,12 @@ enum TextInputResult {
|
||||
TextInputResultError,
|
||||
};
|
||||
|
||||
static void momentum_app_scene_protocols_freqs_add_text_input_callback(void* context) {
|
||||
static void
|
||||
momentum_app_scene_protocols_freqs_add_number_input_callback(void* context, int32_t number) {
|
||||
MomentumApp* app = context;
|
||||
|
||||
char* end;
|
||||
uint32_t value = strtol(app->subghz_freq_buffer, &end, 0) * 1000;
|
||||
if(*end || !furi_hal_subghz_is_frequency_valid(value)) {
|
||||
uint32_t value = number * 1000;
|
||||
if(!furi_hal_subghz_is_frequency_valid(value)) {
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultError);
|
||||
return;
|
||||
}
|
||||
@@ -27,26 +27,24 @@ static void momentum_app_scene_protocols_freqs_add_text_input_callback(void* con
|
||||
|
||||
void momentum_app_scene_protocols_freqs_add_on_enter(void* context) {
|
||||
MomentumApp* app = context;
|
||||
TextInput* text_input = app->text_input;
|
||||
NumberInput* number_input = app->number_input;
|
||||
|
||||
text_input_set_header_text(text_input, "Ex: 123456 for 123.456 MHz");
|
||||
number_input_set_header_text(number_input, "Use kHz values, like 433920");
|
||||
|
||||
strlcpy(app->subghz_freq_buffer, "", sizeof(app->subghz_freq_buffer));
|
||||
|
||||
text_input_set_result_callback(
|
||||
text_input,
|
||||
momentum_app_scene_protocols_freqs_add_text_input_callback,
|
||||
number_input_set_result_callback(
|
||||
number_input,
|
||||
momentum_app_scene_protocols_freqs_add_number_input_callback,
|
||||
app,
|
||||
app->subghz_freq_buffer,
|
||||
sizeof(app->subghz_freq_buffer),
|
||||
true);
|
||||
0,
|
||||
100000,
|
||||
999999);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewTextInput);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewNumberInput);
|
||||
}
|
||||
|
||||
void callback_return(void* context) {
|
||||
MomentumApp* app = context;
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewNumberInput);
|
||||
}
|
||||
|
||||
bool momentum_app_scene_protocols_freqs_add_on_event(void* context, SceneManagerEvent event) {
|
||||
@@ -60,9 +58,15 @@ bool momentum_app_scene_protocols_freqs_add_on_event(void* context, SceneManager
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
break;
|
||||
case TextInputResultError:
|
||||
popup_set_header(app->popup, "Invalid value!", 64, 26, AlignCenter, AlignCenter);
|
||||
popup_set_header(app->popup, "Invalid frequency!", 64, 18, AlignCenter, AlignCenter);
|
||||
popup_set_text(
|
||||
app->popup, "Frequency was not added...", 64, 40, AlignCenter, AlignCenter);
|
||||
app->popup,
|
||||
"Must be 281-361,\n"
|
||||
"378-481, 749-962 MHz",
|
||||
64,
|
||||
40,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
popup_set_callback(app->popup, callback_return);
|
||||
popup_set_context(app->popup, app);
|
||||
popup_set_timeout(app->popup, 1000);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <nfc/protocols/mf_classic/mf_classic.h>
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define NFC_APP_FOLDER ANY_PATH("nfc")
|
||||
#define NFC_APP_FOLDER EXT_PATH("nfc")
|
||||
#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc")
|
||||
|
||||
struct MfUserDict {
|
||||
|
||||
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 96 B |
@@ -42,7 +42,6 @@ NfcApp* nfc_app_alloc(void) {
|
||||
|
||||
instance->view_dispatcher = view_dispatcher_alloc();
|
||||
instance->scene_manager = scene_manager_alloc(&nfc_scene_handlers, instance);
|
||||
view_dispatcher_enable_queue(instance->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
instance->view_dispatcher, nfc_custom_event_callback);
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
#define NFC_TEXT_STORE_SIZE 128
|
||||
#define NFC_BYTE_INPUT_STORE_SIZE 10
|
||||
#define NFC_LOG_SIZE_MAX (1024)
|
||||
#define NFC_APP_FOLDER ANY_PATH("nfc")
|
||||
#define NFC_APP_FOLDER EXT_PATH("nfc")
|
||||
#define NFC_APP_EXTENSION ".nfc"
|
||||
#define NFC_APP_SHADOW_EXTENSION ".shd"
|
||||
#define NFC_APP_FILENAME_PREFIX "NFC"
|
||||
|
||||
@@ -16,6 +16,7 @@ App(
|
||||
# "!helpers/subghz_chat.c",
|
||||
# "!subghz_extended_freq.c",
|
||||
# ],
|
||||
requires=["region"],
|
||||
resources="resources",
|
||||
fap_libs=["hwdrivers"],
|
||||
fap_icon="icon.png",
|
||||
|
||||
|
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 95 B |
@@ -59,7 +59,8 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
default: //if(SubGhzTxRxStartTxStateOk)
|
||||
result = true;
|
||||
subghz_blink_start(subghz);
|
||||
state = SubGhzRpcStateTx;
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateTx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -71,7 +72,8 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz_blink_stop(subghz);
|
||||
result = true;
|
||||
}
|
||||
state = SubGhzRpcStateIdle;
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
|
||||
rpc_system_app_confirm(subghz->rpc_ctx, result);
|
||||
} else if(event.event == SubGhzCustomEventSceneRpcLoad) {
|
||||
bool result = false;
|
||||
|
||||
@@ -102,7 +102,6 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
|
||||
// View Dispatcher
|
||||
subghz->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(subghz->view_dispatcher);
|
||||
|
||||
subghz->scene_manager = scene_manager_alloc(&subghz_scene_handlers, subghz);
|
||||
view_dispatcher_set_event_callback_context(subghz->view_dispatcher, subghz);
|
||||
|
||||
@@ -426,7 +426,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) {
|
||||
|
||||
// Configure radio
|
||||
furi_hal_subghz_reset();
|
||||
furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs);
|
||||
furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_270khz_async_regs);
|
||||
frequency = furi_hal_subghz_set_frequency_and_path(frequency);
|
||||
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
|
||||
|
||||
@@ -478,7 +478,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* file_name = furi_string_alloc();
|
||||
furi_string_set(file_name, ANY_PATH("subghz/test.sub"));
|
||||
furi_string_set(file_name, EXT_PATH("subghz/test.sub"));
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
|
||||
@@ -592,7 +592,7 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context)
|
||||
UNUSED(context);
|
||||
FuriString* file_name;
|
||||
file_name = furi_string_alloc();
|
||||
furi_string_set(file_name, ANY_PATH("subghz/test.sub"));
|
||||
furi_string_set(file_name, EXT_PATH("subghz/test.sub"));
|
||||
uint32_t repeat = 10;
|
||||
uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT
|
||||
|
||||
@@ -959,9 +959,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
subghz_cli_radio_device_power_off();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO
|
||||
if(!furi_hal_subghz_is_tx_allowed(frequency)) {
|
||||
if(!furi_hal_region_is_frequency_allowed(frequency)) {
|
||||
printf(
|
||||
"In your settings/region, only reception on this frequency (%lu) is allowed,\r\n"
|
||||
"the actual operation of the application is not possible\r\n ",
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_subghz_i.h>
|
||||
#include <toolbox/run_parallel.h>
|
||||
#include <subghz/subghz_last_settings.h>
|
||||
#include <flipper_format/flipper_format_i.h>
|
||||
|
||||
void subghz_extended_freq() {
|
||||
#define TAG "SubGhzExtendedFreq"
|
||||
|
||||
static int32_t subghz_extended_freq_apply(void* context) {
|
||||
UNUSED(context);
|
||||
FURI_LOG_D(TAG, "Loading settings");
|
||||
|
||||
bool is_extended_i = false;
|
||||
bool is_bypassed = false;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
@@ -13,6 +19,7 @@ void subghz_extended_freq() {
|
||||
if(flipper_format_file_open_existing(file, "/ext/subghz/assets/extend_range.txt")) {
|
||||
flipper_format_read_bool(file, "use_ext_range_at_own_risk", &is_extended_i, 1);
|
||||
flipper_format_read_bool(file, "ignore_default_tx_region", &is_bypassed, 1);
|
||||
flipper_format_file_close(file);
|
||||
}
|
||||
|
||||
furi_hal_subghz_set_extended_range(is_extended_i);
|
||||
@@ -20,11 +27,29 @@ void subghz_extended_freq() {
|
||||
|
||||
flipper_format_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
// Load external module power amp setting (TODO: move to other place)
|
||||
// TODO: Disable this when external module is not CC1101 E07
|
||||
SubGhzLastSettings* last_settings = subghz_last_settings_alloc();
|
||||
subghz_last_settings_load(last_settings, 0);
|
||||
|
||||
subghz_last_settings_free(last_settings);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void subghz_extended_freq_mount_callback(const void* message, void* context) {
|
||||
UNUSED(context);
|
||||
const StorageEvent* event = message;
|
||||
|
||||
if(event->type == StorageEventTypeCardMount) {
|
||||
run_parallel(subghz_extended_freq_apply, NULL, 1024);
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_extended_freq() {
|
||||
if(!furi_hal_is_normal_boot()) return;
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
furi_pubsub_subscribe(storage_get_pubsub(storage), subghz_extended_freq_mount_callback, NULL);
|
||||
|
||||
if(storage_sd_status(storage) != FSE_OK) {
|
||||
FURI_LOG_D(TAG, "SD Card not ready, skipping settings");
|
||||
} else {
|
||||
subghz_extended_freq_apply(NULL);
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 583 B After Width: | Height: | Size: 98 B |
@@ -4,7 +4,6 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_random.h>
|
||||
#include <littlefs/lfs_util.h> // for lfs_tobe32
|
||||
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <mbedtls/md.h>
|
||||
@@ -319,6 +318,10 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
|
||||
return sizeof(U2fRegisterResp) + cert_len + signature_len + 2;
|
||||
}
|
||||
|
||||
static inline uint32_t u2f_to_big_endian(uint32_t a) {
|
||||
return __builtin_bswap32(a);
|
||||
}
|
||||
|
||||
static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
||||
U2fAuthReq* req = (U2fAuthReq*)buf;
|
||||
U2fAuthResp* resp = (U2fAuthResp*)buf;
|
||||
@@ -348,7 +351,7 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
||||
U2F->user_present = false;
|
||||
|
||||
// The 4 byte counter is represented in big endian. Increment it before use
|
||||
be_u2f_counter = lfs_tobe32(U2F->counter + 1);
|
||||
be_u2f_counter = u2f_to_big_endian(U2F->counter + 1);
|
||||
|
||||
// Generate hash
|
||||
{
|
||||
|
||||
@@ -30,7 +30,6 @@ U2fApp* u2f_app_alloc(void) {
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&u2f_scene_handlers, app);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, u2f_app_tick_event_callback, 500);
|
||||
|
||||