Merge remote-tracking branch 'origin/dev' into nestednonces

This commit is contained in:
noproto 2024-08-15 10:58:32 -04:00
commit 8d1a2203ae
1377 changed files with 3981 additions and 1803 deletions

View File

@ -95,6 +95,22 @@ jobs:
echo "C Lint: all good ✨" >> $GITHUB_STEP_SUMMARY;
fi
- name: 'Check image assets'
if: always()
run: |
set +e;
./fbt -s lint_img 2>&1 | tee lint-assets.log;
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
# Save multiline output
echo "errors=1" >> $GITHUB_OUTPUT;
printf "Image Lint errors:\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY;
echo "$(cat lint-assets.log)" >> $GITHUB_STEP_SUMMARY;
printf "\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY;
exit 1;
else
echo "Image Lint: all good ✨" >> $GITHUB_STEP_SUMMARY;
fi
- name: Report code formatting errors
if: ( steps.syntax_check_py.outputs.errors || steps.syntax_check_cpp.outputs.errors ) && github.event.pull_request
run: |

View File

@ -322,7 +322,12 @@ firmware_env.Append(
"SConstruct",
"firmware.scons",
"fbt_options.py",
]
],
IMG_LINT_SOURCES=[
# Image assets
"applications",
"assets",
],
)
@ -359,6 +364,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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 B

After

Width:  |  Height:  |  Size: 96 B

View File

@ -0,0 +1,8 @@
App(
appid="infrared_test",
name="Infrared Test",
apptype=FlipperAppType.DEBUG,
entry_point="infrared_test_app",
fap_category="Debug",
targets=["f7"],
)

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 B

After

Width:  |  Height:  |  Size: 96 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -665,6 +665,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(
@ -822,6 +829,12 @@ MU_TEST(subghz_encoder_mastercode_test) {
"Test encoder " SUBGHZ_PROTOCOL_MASTERCODE_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");
}
@ -873,6 +886,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);
@ -900,6 +914,7 @@ MU_TEST_SUITE(subghz) {
MU_RUN_TEST(subghz_encoder_holtek_ht12x_test);
MU_RUN_TEST(subghz_encoder_dooya_test);
MU_RUN_TEST(subghz_encoder_mastercode_test);
MU_RUN_TEST(subghz_encoder_dickert_test);
MU_RUN_TEST(subghz_random_test);
subghz_test_deinit();

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 258 B

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 226 B

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

View File

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

View File

@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) ExampleNumberInputScene##id,
typedef enum {
#include "example_number_input_scene_config.h"
ExampleNumberInputSceneNum,
} ExampleNumberInputScene;
#undef ADD_SCENE
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 "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 "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 "example_number_input_scene_config.h"
#undef ADD_SCENE

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 91 B

View File

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

View File

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

View File

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

View File

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

View File

@ -23,7 +23,6 @@ ArchiveApp* archive_alloc(void) {
archive->view_dispatcher = view_dispatcher_alloc();
archive->scene_manager = scene_manager_alloc(&archive_scene_handlers, archive);
view_dispatcher_enable_queue(archive->view_dispatcher);
view_dispatcher_attach_to_gui(
archive->view_dispatcher, archive->gui, ViewDispatcherTypeFullscreen);

View File

@ -15,7 +15,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])) {
if(i == ArchiveFileTypeBadUsb) {
if(furi_string_search(
file->path, archive_get_default_path(ArchiveTabBadUsb)) == 0) {

View File

@ -112,8 +112,6 @@ BadUsbApp* bad_usb_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_usb_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 B

After

Width:  |  Height:  |  Size: 96 B

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 91 B

View File

@ -85,7 +85,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);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 B

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 B

After

Width:  |  Height:  |  Size: 96 B

View File

@ -159,7 +159,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);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 B

After

Width:  |  Height:  |  Size: 95 B

View File

@ -53,7 +53,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);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 B

After

Width:  |  Height:  |  Size: 96 B

View File

@ -41,7 +41,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);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 B

After

Width:  |  Height:  |  Size: 95 B

View File

@ -59,7 +59,6 @@ SubGhz* subghz_alloc(void) {
// 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);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 583 B

After

Width:  |  Height:  |  Size: 98 B

View File

@ -29,7 +29,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);

View File

@ -104,7 +104,7 @@ void animation_manager_set_dummy_mode_state(AnimationManager* animation_manager,
}
}
static void animation_manager_check_blocking_callback(const void* message, void* context) {
static void animation_manager_storage_callback(const void* message, void* context) {
const StorageEvent* storage_event = message;
switch(storage_event->type) {
@ -123,6 +123,22 @@ static void animation_manager_check_blocking_callback(const void* message, void*
}
}
static void animation_manager_dolphin_callback(const void* message, void* context) {
const DolphinPubsubEvent* dolphin_event = message;
switch(*dolphin_event) {
case DolphinPubsubEventUpdate:
furi_assert(context);
AnimationManager* animation_manager = context;
if(animation_manager->check_blocking_callback) {
animation_manager->check_blocking_callback(animation_manager->context);
}
break;
default:
break;
}
}
static void animation_manager_timer_callback(void* context) {
furi_assert(context);
AnimationManager* animation_manager = context;
@ -300,12 +316,12 @@ AnimationManager* animation_manager_alloc(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
animation_manager->pubsub_subscription_storage = furi_pubsub_subscribe(
storage_get_pubsub(storage), animation_manager_check_blocking_callback, animation_manager);
storage_get_pubsub(storage), animation_manager_storage_callback, animation_manager);
furi_record_close(RECORD_STORAGE);
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
animation_manager->pubsub_subscription_dolphin = furi_pubsub_subscribe(
dolphin_get_pubsub(dolphin), animation_manager_check_blocking_callback, animation_manager);
dolphin_get_pubsub(dolphin), animation_manager_dolphin_callback, animation_manager);
furi_record_close(RECORD_DOLPHIN);
animation_manager->blocking_shown_sd_ok = true;

View File

@ -265,7 +265,6 @@ static Desktop* desktop_alloc(void) {
desktop->view_dispatcher = view_dispatcher_alloc();
desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop);
view_dispatcher_enable_queue(desktop->view_dispatcher);
view_dispatcher_attach_to_gui(
desktop->view_dispatcher, desktop->gui, ViewDispatcherTypeDesktop);
view_dispatcher_set_tick_event_callback(

View File

@ -49,12 +49,11 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow
file_browser_start(file_browser, data->preselected_filename);
view_holder_set_view(view_holder, file_browser_get_view(file_browser));
view_holder_start(view_holder);
api_lock_wait_unlock(file_browser_context->lock);
ret = file_browser_context->result;
view_holder_stop(view_holder);
view_holder_set_view(view_holder, NULL);
view_holder_free(view_holder);
file_browser_stop(file_browser);
file_browser_free(file_browser);

View File

@ -88,12 +88,11 @@ DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDa
dialog_ex_set_right_button_text(dialog_ex, message->right_button_text);
view_holder_set_view(view_holder, dialog_ex_get_view(dialog_ex));
view_holder_start(view_holder);
api_lock_wait_unlock(message_context->lock);
ret = message_context->result;
view_holder_stop(view_holder);
view_holder_set_view(view_holder, NULL);
view_holder_free(view_holder);
dialog_ex_free(dialog_ex);
api_lock_free(message_context->lock);

View File

@ -192,8 +192,8 @@ static void dolphin_update_clear_limits_timer_period(void* context) {
FURI_LOG_D(TAG, "Daily limits reset in %lu ms", time_to_clear_limits);
}
static bool dolphin_process_event(FuriMessageQueue* queue, void* context) {
UNUSED(queue);
static bool dolphin_process_event(FuriEventLoopObject* object, void* context) {
UNUSED(object);
Dolphin* dolphin = context;
DolphinEvent event;
@ -204,8 +204,8 @@ static bool dolphin_process_event(FuriMessageQueue* queue, void* context) {
if(event.type == DolphinEventTypeDeed) {
dolphin_state_on_deed(dolphin->state, event.deed);
DolphinPubsubEvent event = DolphinPubsubEventUpdate;
furi_pubsub_publish(dolphin->pubsub, &event);
DolphinPubsubEvent pubsub_event = DolphinPubsubEventUpdate;
furi_pubsub_publish(dolphin->pubsub, &pubsub_event);
furi_event_loop_timer_start(dolphin->butthurt_timer, BUTTHURT_INCREASE_PERIOD_TICKS);
furi_event_loop_timer_start(dolphin->flush_timer, FLUSH_TIMEOUT_TICKS);
@ -280,7 +280,7 @@ int32_t dolphin_srv(void* p) {
dolphin_init_state(dolphin);
furi_event_loop_message_queue_subscribe(
furi_event_loop_subscribe_message_queue(
dolphin->event_loop,
dolphin->event_queue,
FuriEventLoopEventIn,

View File

@ -19,6 +19,7 @@ App(
"view_holder.h",
"modules/button_menu.h",
"modules/byte_input.h",
"modules/number_input.h",
"modules/popup.h",
"modules/text_input.h",
"modules/widget.h",

View File

@ -134,7 +134,7 @@ static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, boo
if((furi_string_empty(ext)) || (furi_string_cmp_str(ext, "*") == 0)) {
return true;
}
if(furi_string_end_with(name, ext)) {
if(furi_string_end_withi(name, ext)) {
return true;
}
}

View File

@ -0,0 +1,443 @@
#include "number_input.h"
#include <gui/elements.h>
#include <furi.h>
#include <assets_icons.h>
struct NumberInput {
View* view;
};
typedef struct {
const char text;
const size_t x;
const size_t y;
} NumberInputKey;
typedef struct {
FuriString* header;
FuriString* text_buffer;
int32_t current_number;
int32_t max_value;
int32_t min_value;
NumberInputCallback callback;
void* callback_context;
size_t selected_row;
size_t selected_column;
} NumberInputModel;
static const size_t keyboard_origin_x = 7;
static const size_t keyboard_origin_y = 31;
static const size_t keyboard_row_count = 2;
static const char enter_symbol = '\r';
static const char backspace_symbol = '\b';
static const char sign_symbol = '-';
static const NumberInputKey keyboard_keys_row_1[] = {
{'0', 0, 12},
{'1', 11, 12},
{'2', 22, 12},
{'3', 33, 12},
{'4', 44, 12},
{backspace_symbol, 103, 4},
};
static const NumberInputKey keyboard_keys_row_2[] = {
{'5', 0, 26},
{'6', 11, 26},
{'7', 22, 26},
{'8', 33, 26},
{'9', 44, 26},
{sign_symbol, 55, 17},
{enter_symbol, 95, 17},
};
static size_t number_input_get_row_size(size_t row_index) {
size_t row_size = 0;
switch(row_index + 1) {
case 1:
row_size = COUNT_OF(keyboard_keys_row_1);
break;
case 2:
row_size = COUNT_OF(keyboard_keys_row_2);
break;
default:
furi_crash();
}
return row_size;
}
static const NumberInputKey* number_input_get_row(size_t row_index) {
const NumberInputKey* row = NULL;
switch(row_index + 1) {
case 1:
row = keyboard_keys_row_1;
break;
case 2:
row = keyboard_keys_row_2;
break;
default:
furi_crash();
}
return row;
}
static void number_input_draw_input(Canvas* canvas, NumberInputModel* model) {
const size_t text_x = 8;
const size_t text_y = 25;
elements_slightly_rounded_frame(canvas, 6, 14, 116, 15);
canvas_draw_str(canvas, text_x, text_y, furi_string_get_cstr(model->text_buffer));
}
static bool number_input_use_sign(NumberInputModel* model) {
//only show sign button if allowed number range needs it
if(model->min_value < 0 && model->max_value >= 0) {
return true;
}
return false;
}
static void number_input_backspace_cb(NumberInputModel* model) {
size_t text_length = furi_string_utf8_length(model->text_buffer);
if(text_length < 1 || (text_length < 2 && model->current_number <= 0)) {
return;
}
furi_string_set_strn(
model->text_buffer, furi_string_get_cstr(model->text_buffer), text_length - 1);
model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
}
static void number_input_handle_up(NumberInputModel* model) {
if(model->selected_row > 0) {
model->selected_row--;
if(model->selected_column > number_input_get_row_size(model->selected_row) - 1) {
model->selected_column = number_input_get_row_size(model->selected_row) - 1;
}
}
}
static void number_input_handle_down(NumberInputModel* model) {
if(model->selected_row < keyboard_row_count - 1) {
if(model->selected_column >= number_input_get_row_size(model->selected_row) - 1) {
model->selected_column = number_input_get_row_size(model->selected_row + 1) - 1;
}
model->selected_row += 1;
}
const NumberInputKey* keys = number_input_get_row(model->selected_row);
if(keys[model->selected_column].text == sign_symbol && !number_input_use_sign(model)) {
model->selected_column--;
}
}
static void number_input_handle_left(NumberInputModel* model) {
if(model->selected_column > 0) {
model->selected_column--;
} else {
model->selected_column = number_input_get_row_size(model->selected_row) - 1;
}
const NumberInputKey* keys = number_input_get_row(model->selected_row);
if(keys[model->selected_column].text == sign_symbol && !number_input_use_sign(model)) {
model->selected_column--;
}
}
static void number_input_handle_right(NumberInputModel* model) {
if(model->selected_column < number_input_get_row_size(model->selected_row) - 1) {
model->selected_column++;
} else {
model->selected_column = 0;
}
const NumberInputKey* keys = number_input_get_row(model->selected_row);
if(keys[model->selected_column].text == sign_symbol && !number_input_use_sign(model)) {
model->selected_column++;
}
}
static bool is_number_too_large(NumberInputModel* model) {
int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10);
if(value > (int64_t)model->max_value) {
return true;
}
return false;
}
static bool is_number_too_small(NumberInputModel* model) {
int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10);
if(value < (int64_t)model->min_value) {
return true;
}
return false;
}
static void number_input_sign(NumberInputModel* model) {
int32_t number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
if(number == 0 && furi_string_cmp_str(model->text_buffer, "-") != 0) {
furi_string_set_str(model->text_buffer, "-");
return;
}
number = number * -1;
furi_string_printf(model->text_buffer, "%ld", number);
if(is_number_too_large(model) || is_number_too_small(model)) {
furi_string_printf(model->text_buffer, "%ld", model->current_number);
return;
}
model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
if(model->current_number == 0) {
furi_string_set_str(model->text_buffer, ""); //show empty if 0, better for usability
}
}
static void number_input_add_digit(NumberInputModel* model, char* newChar) {
furi_string_cat_str(model->text_buffer, newChar);
if((model->max_value >= 0 && is_number_too_large(model)) ||
(model->min_value < 0 && is_number_too_small(model))) {
//you still need to be able to type invalid numbers in some cases to reach valid numbers on later keypress
furi_string_printf(model->text_buffer, "%ld", model->current_number);
return;
}
model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
if(model->current_number == 0) {
furi_string_reset(model->text_buffer);
}
}
static void number_input_handle_ok(NumberInputModel* model) {
char selected = number_input_get_row(model->selected_row)[model->selected_column].text;
char temp_str[2] = {selected, '\0'};
if(selected == enter_symbol) {
if(is_number_too_large(model) || is_number_too_small(model)) {
return; //Do nothing if number outside allowed range
}
model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
model->callback(model->callback_context, model->current_number);
} else if(selected == backspace_symbol) {
number_input_backspace_cb(model);
} else if(selected == sign_symbol) {
number_input_sign(model);
} else {
number_input_add_digit(model, temp_str);
}
}
static void number_input_view_draw_callback(Canvas* canvas, void* _model) {
NumberInputModel* model = _model;
number_input_draw_input(canvas, model);
if(!furi_string_empty(model->header)) {
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 9, furi_string_get_cstr(model->header));
}
canvas_set_font(canvas, FontKeyboard);
// Draw keyboard
for(size_t row = 0; row < keyboard_row_count; row++) {
const size_t column_count = number_input_get_row_size(row);
const NumberInputKey* keys = number_input_get_row(row);
for(size_t column = 0; column < column_count; column++) {
if(keys[column].text == sign_symbol && !number_input_use_sign(model)) {
continue;
}
if(keys[column].text == enter_symbol) {
if(is_number_too_small(model) || is_number_too_large(model)) {
//in some cases you need to be able to type a number smaller/larger than the limits (expl. min = 50, clear all and editor must allow to type 9 and later 0 for 90)
if(model->selected_row == row && model->selected_column == column) {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeySaveBlockedSelected_24x11);
} else {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeySaveBlocked_24x11);
}
} else {
if(model->selected_row == row && model->selected_column == column) {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeySaveSelected_24x11);
} else {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeySave_24x11);
}
}
} else if(keys[column].text == backspace_symbol) {
if(model->selected_row == row && model->selected_column == column) {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeyBackspaceSelected_16x9);
} else {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeyBackspace_16x9);
}
} else if(keys[column].text == sign_symbol) {
if(model->selected_row == row && model->selected_column == column) {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeySignSelected_21x11);
} else {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeySign_21x11);
}
} else {
if(model->selected_row == row && model->selected_column == column) {
canvas_draw_box(
canvas,
keyboard_origin_x + keys[column].x - 3,
keyboard_origin_y + keys[column].y - 10,
11,
13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_glyph(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
keys[column].text);
canvas_set_color(canvas, ColorBlack);
}
}
}
}
static bool number_input_view_input_callback(InputEvent* event, void* context) {
furi_assert(context);
NumberInput* number_input = context;
bool consumed = false;
// Fetch the model
NumberInputModel* model = view_get_model(number_input->view);
if(event->type == InputTypeShort || event->type == InputTypeLong ||
event->type == InputTypeRepeat) {
consumed = true;
switch(event->key) {
case InputKeyLeft:
number_input_handle_left(model);
break;
case InputKeyRight:
number_input_handle_right(model);
break;
case InputKeyUp:
number_input_handle_up(model);
break;
case InputKeyDown:
number_input_handle_down(model);
break;
case InputKeyOk:
number_input_handle_ok(model);
break;
default:
consumed = false;
break;
}
}
// commit view
view_commit_model(number_input->view, consumed);
return consumed;
}
NumberInput* number_input_alloc(void) {
NumberInput* number_input = malloc(sizeof(NumberInput));
number_input->view = view_alloc();
view_set_context(number_input->view, number_input);
view_allocate_model(number_input->view, ViewModelTypeLocking, sizeof(NumberInputModel));
view_set_draw_callback(number_input->view, number_input_view_draw_callback);
view_set_input_callback(number_input->view, number_input_view_input_callback);
with_view_model(
number_input->view,
NumberInputModel * model,
{
model->header = furi_string_alloc();
model->text_buffer = furi_string_alloc();
},
true);
return number_input;
}
void number_input_free(NumberInput* number_input) {
furi_check(number_input);
with_view_model(
number_input->view,
NumberInputModel * model,
{
furi_string_free(model->header);
furi_string_free(model->text_buffer);
},
true);
view_free(number_input->view);
free(number_input);
}
View* number_input_get_view(NumberInput* number_input) {
furi_check(number_input);
return number_input->view;
}
void number_input_set_result_callback(
NumberInput* number_input,
NumberInputCallback callback,
void* callback_context,
int32_t current_number,
int32_t min_value,
int32_t max_value) {
furi_check(number_input);
current_number = CLAMP(current_number, max_value, min_value);
with_view_model(
number_input->view,
NumberInputModel * model,
{
model->callback = callback;
model->callback_context = callback_context;
model->current_number = current_number;
furi_string_printf(model->text_buffer, "%ld", current_number);
model->min_value = min_value;
model->max_value = max_value;
},
true);
}
void number_input_set_header_text(NumberInput* number_input, const char* text) {
furi_check(number_input);
with_view_model(
number_input->view,
NumberInputModel * model,
{ furi_string_set(model->header, text); },
true);
}

View File

@ -0,0 +1,69 @@
/**
* @file number_input.h
* GUI: Integer string keyboard view module API
*/
#pragma once
#include <gui/view.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Number input anonymous structure */
typedef struct NumberInput NumberInput;
/** Callback to be called on save button press */
typedef void (*NumberInputCallback)(void* context, int32_t number);
/** Allocate and initialize Number input.
*
* This Number input is used to enter Numbers (Integers).
*
* @return NumberInput instance pointer
*/
NumberInput* number_input_alloc(void);
/** Deinitialize and free byte input
*
* @param number_input Number input instance
*/
void number_input_free(NumberInput* number_input);
/** Get byte input view
*
* @param number_input byte input instance
*
* @return View instance that can be used for embedding
*/
View* number_input_get_view(NumberInput* number_input);
/** Set byte input result callback
*
* @param number_input byte input instance
* @param input_callback input callback fn
* @param callback_context callback context
* @param[in] current_number The current number
* @param min_value Min number value
* @param max_value Max number value
*/
void number_input_set_result_callback(
NumberInput* number_input,
NumberInputCallback input_callback,
void* callback_context,
int32_t current_number,
int32_t min_value,
int32_t max_value);
/** Set byte input header text
*
* @param number_input byte input instance
* @param text text to be shown
*/
void number_input_set_header_text(NumberInput* number_input, const char* text);
#ifdef __cplusplus
}
#endif

View File

@ -2,6 +2,8 @@
#define TAG "ViewDispatcher"
#define VIEW_DISPATCHER_QUEUE_LEN (16U)
ViewDispatcher* view_dispatcher_alloc(void) {
ViewDispatcher* view_dispatcher = malloc(sizeof(ViewDispatcher));
@ -14,6 +16,26 @@ ViewDispatcher* view_dispatcher_alloc(void) {
ViewDict_init(view_dispatcher->views);
view_dispatcher->event_loop = furi_event_loop_alloc();
view_dispatcher->input_queue =
furi_message_queue_alloc(VIEW_DISPATCHER_QUEUE_LEN, sizeof(InputEvent));
furi_event_loop_subscribe_message_queue(
view_dispatcher->event_loop,
view_dispatcher->input_queue,
FuriEventLoopEventIn,
view_dispatcher_run_input_callback,
view_dispatcher);
view_dispatcher->event_queue =
furi_message_queue_alloc(VIEW_DISPATCHER_QUEUE_LEN, sizeof(uint32_t));
furi_event_loop_subscribe_message_queue(
view_dispatcher->event_loop,
view_dispatcher->event_queue,
FuriEventLoopEventIn,
view_dispatcher_run_event_callback,
view_dispatcher);
return view_dispatcher;
}
@ -29,44 +51,19 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher) {
// Free ViewPort
view_port_free(view_dispatcher->view_port);
// Free internal queue
if(view_dispatcher->input_queue) {
furi_event_loop_message_queue_unsubscribe(
view_dispatcher->event_loop, view_dispatcher->input_queue);
furi_message_queue_free(view_dispatcher->input_queue);
}
if(view_dispatcher->event_queue) {
furi_event_loop_message_queue_unsubscribe(
view_dispatcher->event_loop, view_dispatcher->event_queue);
furi_message_queue_free(view_dispatcher->event_queue);
}
if(view_dispatcher->event_loop) {
furi_event_loop_free(view_dispatcher->event_loop);
}
furi_event_loop_unsubscribe(view_dispatcher->event_loop, view_dispatcher->input_queue);
furi_event_loop_unsubscribe(view_dispatcher->event_loop, view_dispatcher->event_queue);
furi_message_queue_free(view_dispatcher->input_queue);
furi_message_queue_free(view_dispatcher->event_queue);
furi_event_loop_free(view_dispatcher->event_loop);
// Free dispatcher
free(view_dispatcher);
}
void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher) {
furi_check(view_dispatcher);
furi_check(view_dispatcher->event_loop == NULL);
view_dispatcher->event_loop = furi_event_loop_alloc();
view_dispatcher->input_queue = furi_message_queue_alloc(16, sizeof(InputEvent));
furi_event_loop_message_queue_subscribe(
view_dispatcher->event_loop,
view_dispatcher->input_queue,
FuriEventLoopEventIn,
view_dispatcher_run_input_callback,
view_dispatcher);
view_dispatcher->event_queue = furi_message_queue_alloc(16, sizeof(uint32_t));
furi_event_loop_message_queue_subscribe(
view_dispatcher->event_loop,
view_dispatcher->event_queue,
FuriEventLoopEventIn,
view_dispatcher_run_event_callback,
view_dispatcher);
UNUSED(view_dispatcher);
}
void view_dispatcher_set_navigation_event_callback(
@ -99,14 +96,12 @@ void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher,
FuriEventLoop* view_dispatcher_get_event_loop(ViewDispatcher* view_dispatcher) {
furi_check(view_dispatcher);
furi_check(view_dispatcher->event_loop);
return view_dispatcher->event_loop;
}
void view_dispatcher_run(ViewDispatcher* view_dispatcher) {
furi_check(view_dispatcher);
furi_check(view_dispatcher->event_loop);
uint32_t tick_period = view_dispatcher->tick_period == 0 ? FuriWaitForever :
view_dispatcher->tick_period;
@ -134,7 +129,6 @@ void view_dispatcher_run(ViewDispatcher* view_dispatcher) {
void view_dispatcher_stop(ViewDispatcher* view_dispatcher) {
furi_check(view_dispatcher);
furi_check(view_dispatcher->event_loop);
furi_event_loop_stop(view_dispatcher->event_loop);
}
@ -242,13 +236,9 @@ void view_dispatcher_draw_callback(Canvas* canvas, void* context) {
void view_dispatcher_input_callback(InputEvent* event, void* context) {
ViewDispatcher* view_dispatcher = context;
if(view_dispatcher->input_queue) {
furi_check(
furi_message_queue_put(view_dispatcher->input_queue, event, FuriWaitForever) ==
FuriStatusOk);
} else {
view_dispatcher_handle_input(view_dispatcher, event);
}
furi_check(
furi_message_queue_put(view_dispatcher->input_queue, event, FuriWaitForever) ==
FuriStatusOk);
}
void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* event) {
@ -328,7 +318,6 @@ void view_dispatcher_handle_custom_event(ViewDispatcher* view_dispatcher, uint32
void view_dispatcher_send_custom_event(ViewDispatcher* view_dispatcher, uint32_t event) {
furi_check(view_dispatcher);
furi_check(view_dispatcher->event_loop);
furi_check(
furi_message_queue_put(view_dispatcher->event_queue, &event, FuriWaitForever) ==
@ -364,9 +353,7 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie
view_port_update(view_dispatcher->view_port);
} else {
view_port_enabled_set(view_dispatcher->view_port, false);
if(view_dispatcher->event_loop) {
view_dispatcher_stop(view_dispatcher);
}
view_dispatcher_stop(view_dispatcher);
}
}
@ -381,10 +368,10 @@ void view_dispatcher_update(View* view, void* context) {
}
}
bool view_dispatcher_run_event_callback(FuriMessageQueue* queue, void* context) {
bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
ViewDispatcher* instance = context;
furi_assert(instance->event_queue == queue);
furi_assert(instance->event_queue == object);
uint32_t event;
furi_check(furi_message_queue_get(instance->event_queue, &event, 0) == FuriStatusOk);
@ -393,10 +380,10 @@ bool view_dispatcher_run_event_callback(FuriMessageQueue* queue, void* context)
return true;
}
bool view_dispatcher_run_input_callback(FuriMessageQueue* queue, void* context) {
bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
ViewDispatcher* instance = context;
furi_assert(instance->input_queue == queue);
furi_assert(instance->input_queue == object);
InputEvent input;
furi_check(furi_message_queue_get(instance->input_queue, &input, 0) == FuriStatusOk);

View File

@ -2,6 +2,14 @@
* @file view_dispatcher.h
* @brief GUI: ViewDispatcher API
*
* ViewDispatcher is used to connect several Views to a Gui instance, switch between them and handle various events.
* This is useful in applications featuring an advanced graphical user interface.
*
* Internally, ViewDispatcher employs a FuriEventLoop instance together with two separate
* message queues for input and custom event handling. See FuriEventLoop for more information.
*
* If no multi-view or complex event handling capabilities are required, consider using ViewHolder instead.
*
* @warning Views added to a ViewDispatcher MUST NOT be in a ViewStack at the same time.
*/
@ -40,6 +48,9 @@ typedef void (*ViewDispatcherTickEventCallback)(void* context);
ViewDispatcher* view_dispatcher_alloc(void);
/** Free ViewDispatcher instance
*
* @warning All added views MUST be removed using view_dispatcher_remove_view()
* before calling this function.
*
* @param view_dispatcher pointer to ViewDispatcher
*/
@ -47,12 +58,13 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher);
/** Enable queue support
*
* Allocates event_loop, input and event message queues. Must be used with
* `view_dispatcher_run`
* @deprecated Do NOT use in new code and remove all calls to it from existing code.
* The queue support is now always enabled during construction. If no queue support
* is required, consider using ViewHolder instead.
*
* @param view_dispatcher ViewDispatcher instance
*/
void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher);
FURI_DEPRECATED void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher);
/** Send custom event
*
@ -103,11 +115,11 @@ void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher,
/** Get event_loop instance
*
* event_loop instance is allocated on `view_dispatcher_enable_queue` and used
* in view_dispatcher_run.
* Use the return value to connect additional supported primitives (message queues, timers, etc)
* to this ViewDispatcher instance's event loop.
*
* You can add your objects into event_loop instance, but don't run the loop on
* your side as it will cause issues with input processing on dispatcher stop.
* @warning Do NOT call furi_event_loop_run() on the returned instance, it is done internally
* in the view_dispatcher_run() call.
*
* @param view_dispatcher ViewDispatcher instance
*
@ -117,15 +129,14 @@ FuriEventLoop* view_dispatcher_get_event_loop(ViewDispatcher* view_dispatcher);
/** Run ViewDispatcher
*
* Use only after queue enabled
* This function will start the event loop and block until view_dispatcher_stop() is called
* or the current thread receives a FuriSignalExit signal.
*
* @param view_dispatcher ViewDispatcher instance
*/
void view_dispatcher_run(ViewDispatcher* view_dispatcher);
/** Stop ViewDispatcher
*
* Use only after queue enabled
*
* @param view_dispatcher ViewDispatcher instance
*/

View File

@ -56,7 +56,7 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie
void view_dispatcher_update(View* view, void* context);
/** ViewDispatcher run event loop event callback */
bool view_dispatcher_run_event_callback(FuriMessageQueue* queue, void* context);
bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context);
/** ViewDispatcher run event loop input callback */
bool view_dispatcher_run_input_callback(FuriMessageQueue* queue, void* context);
bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context);

View File

@ -32,7 +32,8 @@ ViewHolder* view_holder_alloc(void) {
}
void view_holder_free(ViewHolder* view_holder) {
furi_assert(view_holder);
furi_check(view_holder);
furi_check(view_holder->view == NULL);
if(view_holder->gui) {
gui_remove_view_port(view_holder->gui, view_holder->view_port);
@ -48,12 +49,14 @@ void view_holder_free(ViewHolder* view_holder) {
}
void view_holder_set_view(ViewHolder* view_holder, View* view) {
furi_assert(view_holder);
furi_check(view_holder);
if(view_holder->view) {
if(view_holder->view->exit_callback) {
view_holder->view->exit_callback(view_holder->view->context);
while(view_holder->ongoing_input) {
furi_delay_tick(1);
}
view_exit(view_holder->view);
view_set_update_callback(view_holder->view, NULL);
view_set_update_callback_context(view_holder->view, NULL);
}
@ -61,12 +64,23 @@ void view_holder_set_view(ViewHolder* view_holder, View* view) {
view_holder->view = view;
if(view_holder->view) {
const ViewPortOrientation orientation = (ViewPortOrientation)view->orientation;
furi_assert(orientation < ViewPortOrientationMAX);
if(view_port_get_orientation(view_holder->view_port) != orientation) {
view_port_set_orientation(view_holder->view_port, orientation);
// we just rotated input keys, now it's time to sacrifice some input
view_holder->ongoing_input = 0;
}
view_set_update_callback(view_holder->view, view_holder_update);
view_set_update_callback_context(view_holder->view, view_holder);
if(view_holder->view->enter_callback) {
view_holder->view->enter_callback(view_holder->view->context);
}
view_enter(view_holder->view);
view_port_enabled_set(view_holder->view_port, true);
view_port_update(view_holder->view_port);
} else {
view_port_enabled_set(view_holder->view_port, false);
}
}
@ -74,7 +88,7 @@ void view_holder_set_free_callback(
ViewHolder* view_holder,
FreeCallback free_callback,
void* free_context) {
furi_assert(view_holder);
furi_check(view_holder);
view_holder->free_callback = free_callback;
view_holder->free_context = free_context;
}
@ -87,31 +101,22 @@ void view_holder_set_back_callback(
ViewHolder* view_holder,
BackCallback back_callback,
void* back_context) {
furi_assert(view_holder);
furi_check(view_holder);
view_holder->back_callback = back_callback;
view_holder->back_context = back_context;
}
void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui) {
furi_assert(gui);
furi_assert(view_holder);
view_holder->gui = gui;
furi_check(view_holder);
furi_check(view_holder->gui == NULL);
furi_check(gui);
gui_add_view_port(gui, view_holder->view_port, GuiLayerFullscreen);
}
void view_holder_start(ViewHolder* view_holder) {
view_port_enabled_set(view_holder->view_port, true);
}
void view_holder_stop(ViewHolder* view_holder) {
while(view_holder->ongoing_input)
furi_delay_tick(1);
view_port_enabled_set(view_holder->view_port, false);
view_holder->gui = gui;
}
void view_holder_update(View* view, void* context) {
furi_assert(view);
furi_assert(context);
furi_check(view);
furi_check(context);
ViewHolder* view_holder = context;
if(view == view_holder->view) {
@ -119,6 +124,18 @@ void view_holder_update(View* view, void* context) {
}
}
void view_holder_send_to_front(ViewHolder* view_holder) {
furi_check(view_holder);
furi_check(view_holder->gui);
gui_view_port_send_to_front(view_holder->gui, view_holder->view_port);
}
void view_holder_send_to_back(ViewHolder* view_holder) {
furi_check(view_holder);
furi_check(view_holder->gui);
gui_view_port_send_to_back(view_holder->gui, view_holder->view_port);
}
static void view_holder_draw_callback(Canvas* canvas, void* context) {
ViewHolder* view_holder = context;
if(view_holder->view) {

View File

@ -2,7 +2,10 @@
* @file view_holder.h
* @brief GUI: ViewHolder API
*
* @warning View added to a ViewHolder MUST NOT be in a ViewStack at the same time.
* ViewHolder is used to connect a single View to a Gui instance. This is useful in smaller applications
* with a simple user interface. If advanced view switching capabilites are required, consider using ViewDispatcher instead.
*
* @warning Views added to a ViewHolder MUST NOT be in a ViewStack at the same time.
*/
#pragma once
@ -22,7 +25,8 @@ typedef void (*FreeCallback)(void* free_context);
/**
* @brief Back callback type
* @warning comes from GUI thread
*
* @warning Will be called from the GUI thread
*/
typedef void (*BackCallback)(void* back_context);
@ -34,12 +38,17 @@ ViewHolder* view_holder_alloc(void);
/**
* @brief Free ViewHolder and call Free callback
*
* @warning The current view must be unset prior to freeing a ViewHolder instance.
*
* @param view_holder pointer to ViewHolder
*/
void view_holder_free(ViewHolder* view_holder);
/**
* @brief Set view for ViewHolder
*
* Pass NULL as the view parameter to unset the current view.
*
* @param view_holder ViewHolder instance
* @param view View instance
@ -59,13 +68,25 @@ void view_holder_set_free_callback(
void* free_context);
/**
* @brief Free callback context getter. Useful if your Free callback is a module destructor, so you can get an instance of the module using this method.
* @brief Free callback context getter.
*
* Useful if your Free callback is a module destructor, so you can get an instance of the module using this method.
*
* @param view_holder ViewHolder instance
* @return void* free callback context
*/
void* view_holder_get_free_context(ViewHolder* view_holder);
/**
* @brief Set the back key callback.
*
* The callback function will be called if the user has pressed the Back key
* and the current view did not handle this event.
*
* @param view_holder ViewHolder instance
* @param back_callback pointer to the callback function
* @param back_context pointer to a user-specific object, can be NULL
*/
void view_holder_set_back_callback(
ViewHolder* view_holder,
BackCallback back_callback,
@ -80,26 +101,27 @@ void view_holder_set_back_callback(
void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui);
/**
* @brief Enable view processing
*
* @param view_holder
*/
void view_holder_start(ViewHolder* view_holder);
/**
* @brief Disable view processing
*
* @param view_holder
*/
void view_holder_stop(ViewHolder* view_holder);
/** View Update Handler
* @brief View Update Handler
*
* @param view View Instance
* @param context ViewHolder instance
* @param view View Instance
* @param context ViewHolder instance
*/
void view_holder_update(View* view, void* context);
/**
* @brief Send ViewPort of this ViewHolder instance to front
*
* @param view_holder ViewHolder instance
*/
void view_holder_send_to_front(ViewHolder* view_holder);
/**
* @brief Send ViewPort of this ViewHolder instance to back
*
* @param view_holder ViewHolder instance
*/
void view_holder_send_to_back(ViewHolder* view_holder);
#ifdef __cplusplus
}
#endif

View File

@ -61,7 +61,6 @@ static LoaderApplicationsApp* loader_applications_app_alloc(void) {
app->loading = loading_alloc();
view_holder_attach_to_gui(app->view_holder, app->gui);
view_holder_set_view(app->view_holder, loading_get_view(app->loading));
return app;
} //-V773
@ -149,7 +148,7 @@ static int32_t loader_applications_thread(void* p) {
LoaderApplicationsApp* app = loader_applications_app_alloc();
// start loading animation
view_holder_start(app->view_holder);
view_holder_set_view(app->view_holder, loading_get_view(app->loading));
while(loader_applications_select_app(app)) {
if(!furi_string_end_with(app->file_path, ".js")) {
@ -161,7 +160,7 @@ static int32_t loader_applications_thread(void* p) {
}
// stop loading animation
view_holder_stop(app->view_holder);
view_holder_set_view(app->view_holder, NULL);
loader_applications_app_free(app);

View File

@ -158,8 +158,6 @@ static LoaderMenuApp* loader_menu_app_alloc(LoaderMenu* loader_menu) {
view_set_context(settings_view, app->settings_menu);
view_set_previous_callback(settings_view, loader_menu_switch_to_primary);
view_dispatcher_add_view(app->view_dispatcher, LoaderMenuViewSettings, settings_view);
view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_switch_to_view(app->view_dispatcher, LoaderMenuViewPrimary);
return app;

View File

@ -17,13 +17,15 @@ void power_cli_off(Cli* cli, FuriString* args) {
void power_cli_reboot(Cli* cli, FuriString* args) {
UNUSED(cli);
UNUSED(args);
power_reboot(PowerBootModeNormal);
Power* power = furi_record_open(RECORD_POWER);
power_reboot(power, PowerBootModeNormal);
}
void power_cli_reboot2dfu(Cli* cli, FuriString* args) {
UNUSED(cli);
UNUSED(args);
power_reboot(PowerBootModeDfu);
Power* power = furi_record_open(RECORD_POWER);
power_reboot(power, PowerBootModeDfu);
}
void power_cli_5v(Cli* cli, FuriString* args) {

View File

@ -3,17 +3,25 @@
#include <furi.h>
#include <furi_hal.h>
#include <update_util/update_operation.h>
#include <notification/notification_messages.h>
#define TAG "Power"
#define POWER_OFF_TIMEOUT 90
#define POWER_OFF_TIMEOUT_S (90U)
#define POWER_POLL_PERIOD_MS (1000UL)
void power_draw_battery_callback(Canvas* canvas, void* context) {
#define POWER_VBUS_LOW_THRESHOLD (4.0f)
#define POWER_HEALTH_LOW_THRESHOLD (70U)
static void power_draw_battery_callback(Canvas* canvas, void* context) {
furi_assert(context);
Power* power = context;
canvas_draw_icon(canvas, 0, 0, &I_Battery_26x8);
if(power->info.gauge_is_ok) {
canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4);
if(power->info.voltage_battery_charge_limit < 4.2f) {
// Battery charge voltage limit is modified, indicate with cross pattern
canvas_invert_color(canvas);
@ -28,6 +36,7 @@ void power_draw_battery_callback(Canvas* canvas, void* context) {
}
canvas_invert_color(canvas);
}
if(power->state == PowerStateCharging) {
canvas_set_bitmap_mode(canvas, 1);
canvas_set_color(canvas, ColorWhite);
@ -37,6 +46,7 @@ void power_draw_battery_callback(Canvas* canvas, void* context) {
canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_9x10);
canvas_set_bitmap_mode(canvas, 0);
}
} else {
canvas_draw_box(canvas, 8, 3, 8, 2);
}
@ -46,99 +56,61 @@ static ViewPort* power_battery_view_port_alloc(Power* power) {
ViewPort* battery_view_port = view_port_alloc();
view_port_set_width(battery_view_port, icon_get_width(&I_Battery_26x8));
view_port_draw_callback_set(battery_view_port, power_draw_battery_callback, power);
gui_add_view_port(power->gui, battery_view_port, GuiLayerStatusBarRight);
return battery_view_port;
}
Power* power_alloc(void) {
Power* power = malloc(sizeof(Power));
static bool power_update_info(Power* power) {
const PowerInfo info = {
.is_charging = furi_hal_power_is_charging(),
.gauge_is_ok = furi_hal_power_gauge_is_ok(),
.is_shutdown_requested = furi_hal_power_is_shutdown_requested(),
.charge = furi_hal_power_get_pct(),
.health = furi_hal_power_get_bat_health_pct(),
.capacity_remaining = furi_hal_power_get_battery_remaining_capacity(),
.capacity_full = furi_hal_power_get_battery_full_capacity(),
.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger),
.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge),
.voltage_battery_charge_limit = furi_hal_power_get_battery_charge_voltage_limit(),
.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger),
.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge),
.voltage_vbus = furi_hal_power_get_usb_voltage(),
.temperature_charger = furi_hal_power_get_battery_temperature(FuriHalPowerICCharger),
.temperature_gauge = furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge),
};
// Records
power->notification = furi_record_open(RECORD_NOTIFICATION);
power->gui = furi_record_open(RECORD_GUI);
// Pubsub
power->event_pubsub = furi_pubsub_alloc();
// State initialization
power->state = PowerStateNotCharging;
power->battery_low = false;
power->power_off_timeout = POWER_OFF_TIMEOUT;
power->api_mtx = furi_mutex_alloc(FuriMutexTypeNormal);
// Gui
power->view_dispatcher = view_dispatcher_alloc();
power->power_off = power_off_alloc();
view_dispatcher_add_view(
power->view_dispatcher, PowerViewOff, power_off_get_view(power->power_off));
power->power_unplug_usb = power_unplug_usb_alloc();
view_dispatcher_add_view(
power->view_dispatcher,
PowerViewUnplugUsb,
power_unplug_usb_get_view(power->power_unplug_usb));
view_dispatcher_attach_to_gui(
power->view_dispatcher, power->gui, ViewDispatcherTypeFullscreen);
// Battery view port
power->battery_view_port = power_battery_view_port_alloc(power);
power->show_low_bat_level_message = true;
return power;
const bool need_refresh = (power->info.charge != info.charge) ||
(power->info.is_charging != info.is_charging);
power->info = info;
return need_refresh;
}
static void power_check_charging_state(Power* power) {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
if(furi_hal_power_is_charging()) {
if((power->info.charge == 100) || (furi_hal_power_is_charging_done())) {
if(power->state != PowerStateCharged) {
notification_internal_message(power->notification, &sequence_charged);
notification_internal_message(notification, &sequence_charged);
power->state = PowerStateCharged;
power->event.type = PowerEventTypeFullyCharged;
furi_pubsub_publish(power->event_pubsub, &power->event);
}
} else {
if(power->state != PowerStateCharging) {
notification_internal_message(power->notification, &sequence_charging);
power->state = PowerStateCharging;
power->event.type = PowerEventTypeStartCharging;
furi_pubsub_publish(power->event_pubsub, &power->event);
}
}
} else {
if(power->state != PowerStateNotCharging) {
notification_internal_message(power->notification, &sequence_not_charging);
power->state = PowerStateNotCharging;
power->event.type = PowerEventTypeStopCharging;
} else if(power->state != PowerStateCharging) {
notification_internal_message(notification, &sequence_charging);
power->state = PowerStateCharging;
power->event.type = PowerEventTypeStartCharging;
furi_pubsub_publish(power->event_pubsub, &power->event);
}
} else if(power->state != PowerStateNotCharging) {
notification_internal_message(notification, &sequence_not_charging);
power->state = PowerStateNotCharging;
power->event.type = PowerEventTypeStopCharging;
furi_pubsub_publish(power->event_pubsub, &power->event);
}
}
static bool power_update_info(Power* power) {
PowerInfo info;
info.is_charging = furi_hal_power_is_charging();
info.gauge_is_ok = furi_hal_power_gauge_is_ok();
info.is_shutdown_requested = furi_hal_power_is_shutdown_requested();
info.charge = furi_hal_power_get_pct();
info.health = furi_hal_power_get_bat_health_pct();
info.capacity_remaining = furi_hal_power_get_battery_remaining_capacity();
info.capacity_full = furi_hal_power_get_battery_full_capacity();
info.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger);
info.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge);
info.voltage_battery_charge_limit = furi_hal_power_get_battery_charge_voltage_limit();
info.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger);
info.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge);
info.voltage_vbus = furi_hal_power_get_usb_voltage();
info.temperature_charger = furi_hal_power_get_battery_temperature(FuriHalPowerICCharger);
info.temperature_gauge = furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge);
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
bool need_refresh = power->info.charge != info.charge;
need_refresh |= power->info.is_charging != info.is_charging;
power->info = info;
furi_mutex_release(power->api_mtx);
return need_refresh;
furi_record_close(RECORD_NOTIFICATION);
}
static void power_check_low_battery(Power* power) {
@ -147,40 +119,41 @@ static void power_check_low_battery(Power* power) {
}
// Check battery charge and vbus voltage
if((power->info.is_shutdown_requested) && (power->info.voltage_vbus < 4.0f) &&
power->show_low_bat_level_message) {
if((power->info.is_shutdown_requested) &&
(power->info.voltage_vbus < POWER_VBUS_LOW_THRESHOLD) && power->show_battery_low_warning) {
if(!power->battery_low) {
view_dispatcher_send_to_front(power->view_dispatcher);
view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewOff);
view_holder_send_to_front(power->view_holder);
view_holder_set_view(power->view_holder, power_off_get_view(power->view_power_off));
}
power->battery_low = true;
} else {
if(power->battery_low) {
view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
power->power_off_timeout = POWER_OFF_TIMEOUT;
// view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
view_holder_set_view(power->view_holder, NULL);
power->power_off_timeout = POWER_OFF_TIMEOUT_S;
}
power->battery_low = false;
}
// If battery low, update view and switch off power after timeout
if(power->battery_low) {
PowerOffResponse response = power_off_get_response(power->power_off);
PowerOffResponse response = power_off_get_response(power->view_power_off);
if(response == PowerOffResponseDefault) {
if(power->power_off_timeout) {
power_off_set_time_left(power->power_off, power->power_off_timeout--);
power_off_set_time_left(power->view_power_off, power->power_off_timeout--);
} else {
power_off(power);
}
} else if(response == PowerOffResponseOk) {
power_off(power);
} else if(response == PowerOffResponseHide) {
view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
view_holder_set_view(power->view_holder, NULL);
if(power->power_off_timeout) {
power_off_set_time_left(power->power_off, power->power_off_timeout--);
power_off_set_time_left(power->view_power_off, power->power_off_timeout--);
} else {
power_off(power);
}
} else if(response == PowerOffResponseCancel) {
view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
view_holder_set_view(power->view_holder, NULL);
}
}
}
@ -194,6 +167,121 @@ static void power_check_battery_level_change(Power* power) {
}
}
static void power_handle_shutdown(Power* power) {
furi_hal_power_off();
// Notify user if USB is plugged
view_holder_send_to_front(power->view_holder);
view_holder_set_view(
power->view_holder, power_unplug_usb_get_view(power->view_power_unplug_usb));
furi_delay_ms(100);
furi_halt("Disconnect USB for safe shutdown");
}
static void power_handle_reboot(PowerBootMode mode) {
if(mode == PowerBootModeNormal) {
update_operation_disarm();
} else if(mode == PowerBootModeDfu) {
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeDfu);
} else if(mode == PowerBootModeUpdateStart) {
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePreUpdate);
} else {
furi_crash();
}
furi_hal_power_reset();
}
static bool power_message_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
Power* power = context;
furi_assert(object == power->message_queue);
PowerMessage msg;
furi_check(furi_message_queue_get(power->message_queue, &msg, 0) == FuriStatusOk);
switch(msg.type) {
case PowerMessageTypeShutdown:
power_handle_shutdown(power);
break;
case PowerMessageTypeReboot:
power_handle_reboot(msg.boot_mode);
break;
case PowerMessageTypeGetInfo:
*msg.power_info = power->info;
break;
case PowerMessageTypeIsBatteryHealthy:
*msg.bool_param = power->info.health > POWER_HEALTH_LOW_THRESHOLD;
break;
case PowerMessageTypeShowBatteryLowWarning:
power->show_battery_low_warning = *msg.bool_param;
break;
default:
furi_crash();
}
if(msg.lock) {
api_lock_unlock(msg.lock);
}
return true;
}
static void power_tick_callback(void* context) {
furi_assert(context);
Power* power = context;
// Update data from gauge and charger
const bool need_refresh = power_update_info(power);
// Check low battery level
power_check_low_battery(power);
// Check and notify about charging state
power_check_charging_state(power);
// Check and notify about battery level change
power_check_battery_level_change(power);
// Update battery view port
if(need_refresh) {
view_port_update(power->battery_view_port);
}
// Check OTG status and disable it in case of fault
if(furi_hal_power_is_otg_enabled()) {
furi_hal_power_check_otg_status();
}
}
static Power* power_alloc(void) {
Power* power = malloc(sizeof(Power));
// Pubsub
power->event_pubsub = furi_pubsub_alloc();
// State initialization
power->power_off_timeout = POWER_OFF_TIMEOUT_S;
power->show_battery_low_warning = true;
// Gui
Gui* gui = furi_record_open(RECORD_GUI);
power->view_holder = view_holder_alloc();
power->view_power_off = power_off_alloc();
power->view_power_unplug_usb = power_unplug_usb_alloc();
view_holder_attach_to_gui(power->view_holder, gui);
// Battery view port
power->battery_view_port = power_battery_view_port_alloc(power);
gui_add_view_port(gui, power->battery_view_port, GuiLayerStatusBarRight);
// Event loop
power->event_loop = furi_event_loop_alloc();
power->message_queue = furi_message_queue_alloc(4, sizeof(PowerMessage));
furi_event_loop_subscribe_message_queue(
power->event_loop,
power->message_queue,
FuriEventLoopEventIn,
power_message_callback,
power);
furi_event_loop_tick_set(power->event_loop, 1000, power_tick_callback, power);
return power;
}
int32_t power_srv(void* p) {
UNUSED(p);
@ -206,33 +294,9 @@ int32_t power_srv(void* p) {
Power* power = power_alloc();
power_update_info(power);
furi_record_create(RECORD_POWER, power);
while(1) {
// Update data from gauge and charger
bool need_refresh = power_update_info(power);
// Check low battery level
power_check_low_battery(power);
// Check and notify about charging state
power_check_charging_state(power);
// Check and notify about battery level change
power_check_battery_level_change(power);
// Update battery view port
if(need_refresh) view_port_update(power->battery_view_port);
// Check OTG status and disable it in case of fault
if(furi_hal_power_is_otg_enabled()) {
furi_hal_power_check_otg_status();
}
furi_delay_ms(1000);
}
furi_crash("That was unexpected");
furi_event_loop_run(power->event_loop);
return 0;
}

View File

@ -1,9 +1,10 @@
#pragma once
#include <stdint.h>
#include <core/pubsub.h>
#include <stdbool.h>
#include <core/pubsub.h>
#ifdef __cplusplus
extern "C" {
#endif
@ -65,7 +66,7 @@ void power_off(Power* power);
*
* @param mode PowerBootMode
*/
void power_reboot(PowerBootMode mode);
void power_reboot(Power* power, PowerBootMode mode);
/** Get power info
*

View File

@ -1,41 +1,39 @@
#include "power_i.h"
#include <furi.h>
#include <furi_hal.h>
#include <update_util/update_operation.h>
void power_off(Power* power) {
furi_check(power);
furi_hal_power_off();
// Notify user if USB is plugged
view_dispatcher_send_to_front(power->view_dispatcher);
view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewUnplugUsb);
furi_delay_ms(100);
furi_halt("Disconnect USB for safe shutdown");
PowerMessage msg = {
.type = PowerMessageTypeShutdown,
};
furi_check(
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
}
void power_reboot(PowerBootMode mode) {
if(mode == PowerBootModeNormal) {
update_operation_disarm();
} else if(mode == PowerBootModeDfu) {
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeDfu);
} else if(mode == PowerBootModeUpdateStart) {
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePreUpdate);
} else {
furi_crash();
}
void power_reboot(Power* power, PowerBootMode mode) {
PowerMessage msg = {
.type = PowerMessageTypeReboot,
.boot_mode = mode,
};
furi_hal_power_reset();
furi_check(
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
}
void power_get_info(Power* power, PowerInfo* info) {
furi_check(power);
furi_check(info);
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
memcpy(info, &power->info, sizeof(power->info));
furi_mutex_release(power->api_mtx);
PowerMessage msg = {
.type = PowerMessageTypeGetInfo,
.power_info = info,
.lock = api_lock_alloc_locked(),
};
furi_check(
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
api_lock_wait_unlock_and_free(msg.lock);
}
FuriPubSub* power_get_pubsub(Power* power) {
@ -45,16 +43,30 @@ FuriPubSub* power_get_pubsub(Power* power) {
bool power_is_battery_healthy(Power* power) {
furi_check(power);
bool is_healthy = false;
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
is_healthy = power->info.health > POWER_BATTERY_HEALTHY_LEVEL;
furi_mutex_release(power->api_mtx);
return is_healthy;
bool ret = false;
PowerMessage msg = {
.type = PowerMessageTypeIsBatteryHealthy,
.lock = api_lock_alloc_locked(),
.bool_param = &ret,
};
furi_check(
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
api_lock_wait_unlock_and_free(msg.lock);
return ret;
}
void power_enable_low_battery_level_notification(Power* power, bool enable) {
furi_check(power);
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
power->show_low_bat_level_message = enable;
furi_mutex_release(power->api_mtx);
PowerMessage msg = {
.type = PowerMessageTypeShowBatteryLowWarning,
.bool_param = &enable,
};
furi_check(
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
}

View File

@ -2,19 +2,15 @@
#include "power.h"
#include <stdint.h>
#include <gui/view_dispatcher.h>
#include <gui/gui.h>
#include <gui/view_holder.h>
#include <toolbox/api_lock.h>
#include <assets_icons.h>
#include <gui/modules/popup.h>
#include "views/power_off.h"
#include "views/power_unplug_usb.h"
#include <notification/notification_messages.h>
#define POWER_BATTERY_HEALTHY_LEVEL 70
typedef enum {
PowerStateNotCharging,
PowerStateCharging,
@ -22,28 +18,44 @@ typedef enum {
} PowerState;
struct Power {
ViewDispatcher* view_dispatcher;
PowerOff* power_off;
PowerUnplugUsb* power_unplug_usb;
ViewHolder* view_holder;
FuriPubSub* event_pubsub;
FuriEventLoop* event_loop;
FuriMessageQueue* message_queue;
ViewPort* battery_view_port;
Gui* gui;
NotificationApp* notification;
FuriPubSub* event_pubsub;
PowerEvent event;
PowerOff* view_power_off;
PowerUnplugUsb* view_power_unplug_usb;
PowerEvent event;
PowerState state;
PowerInfo info;
bool battery_low;
bool show_low_bat_level_message;
bool show_battery_low_warning;
uint8_t battery_level;
uint8_t power_off_timeout;
FuriMutex* api_mtx;
};
typedef enum {
PowerViewOff,
PowerViewUnplugUsb,
} PowerView;
typedef enum {
PowerMessageTypeShutdown,
PowerMessageTypeReboot,
PowerMessageTypeGetInfo,
PowerMessageTypeIsBatteryHealthy,
PowerMessageTypeShowBatteryLowWarning,
} PowerMessageType;
typedef struct {
PowerMessageType type;
union {
PowerBootMode boot_mode;
PowerInfo* power_info;
bool* bool_param;
};
FuriApiLock lock;
} PowerMessage;

View File

@ -54,18 +54,21 @@ static void rpc_system_system_reboot_process(const PB_Main* request, void* conte
RpcSession* session = (RpcSession*)context;
furi_assert(session);
Power* power = furi_record_open(RECORD_POWER);
const int mode = request->content.system_reboot_request.mode;
if(mode == PB_System_RebootRequest_RebootMode_OS) {
power_reboot(PowerBootModeNormal);
power_reboot(power, PowerBootModeNormal);
} else if(mode == PB_System_RebootRequest_RebootMode_DFU) {
power_reboot(PowerBootModeDfu);
power_reboot(power, PowerBootModeDfu);
} else if(mode == PB_System_RebootRequest_RebootMode_UPDATE) {
power_reboot(PowerBootModeUpdateStart);
power_reboot(power, PowerBootModeUpdateStart);
} else {
rpc_send_and_release_empty(
session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
}
furi_record_close(RECORD_POWER);
}
static void rpc_system_system_device_info_callback(
@ -181,9 +184,9 @@ static void rpc_system_system_factory_reset_process(const PB_Main* request, void
furi_hal_rtc_reset_registers();
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
power_reboot(PowerBootModeNormal);
(void)session;
Power* power = furi_record_open(RECORD_POWER);
power_reboot(power, PowerBootModeNormal);
}
static void

View File

@ -102,11 +102,6 @@ int32_t storage_srv(void* p) {
Storage* app = storage_app_alloc();
furi_record_create(RECORD_STORAGE, app);
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStorageFormatInternal)) {
FURI_LOG_W(TAG, "Format Internal not supported, clearing flag");
furi_hal_rtc_reset_flag(FuriHalRtcFlagStorageFormatInternal);
}
StorageMessage message;
while(1) {
if(furi_message_queue_get(app->message_queue, &message, STORAGE_TICK) == FuriStatusOk) {

View File

@ -675,9 +675,12 @@ static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context)
char c = cli_getc(cli);
if(c == 'y' || c == 'Y') {
printf("Data will be wiped after reboot.\r\n");
furi_hal_rtc_reset_registers();
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
power_reboot(PowerBootModeNormal);
Power* power = furi_record_open(RECORD_POWER);
power_reboot(power, PowerBootModeNormal);
} else {
printf("Safe choice.\r\n");
}

View File

@ -0,0 +1,3 @@
#pragma once
#define STORAGE_INTERNAL_DIR_NAME ".int"

View File

@ -1,7 +1,9 @@
#include "storage_processing.h"
#include <m-list.h>
#include <m-dict.h>
#include "storage_processing.h"
#include "storage_internal_dirname_i.h"
#define TAG "Storage"
#define STORAGE_PATH_PREFIX_LEN 4u
@ -555,9 +557,9 @@ void storage_process_alias(
} else if(furi_string_start_with(path, STORAGE_INT_PATH_PREFIX)) {
furi_string_replace_at(
path, 0, strlen(STORAGE_INT_PATH_PREFIX), STORAGE_EXT_PATH_PREFIX "/.int");
path, 0, strlen(STORAGE_INT_PATH_PREFIX), EXT_PATH(STORAGE_INTERNAL_DIR_NAME));
FuriString* int_on_ext_path = furi_string_alloc_set(STORAGE_EXT_PATH_PREFIX "/.int");
FuriString* int_on_ext_path = furi_string_alloc_set(EXT_PATH(STORAGE_INTERNAL_DIR_NAME));
if(storage_process_common_stat(app, int_on_ext_path, NULL) != FSE_OK) {
storage_process_common_mkdir(app, int_on_ext_path);
}

View File

@ -1,10 +1,13 @@
#include "fatfs.h"
#include "../filesystem_api_internal.h"
#include "storage_ext.h"
#include <fatfs.h>
#include <furi_hal.h>
#include "sd_notify.h"
#include <furi_hal_sd.h>
#include "sd_notify.h"
#include "storage_ext.h"
#include "../filesystem_api_internal.h"
#include "../storage_internal_dirname_i.h"
typedef FIL SDFile;
typedef DIR SDDir;
typedef FILINFO SDFileInfo;
@ -93,6 +96,64 @@ static bool sd_mount_card_internal(StorageData* storage, bool notify) {
return result;
}
static bool sd_remove_recursive(const char* path) {
SDDir* current_dir = malloc(sizeof(DIR));
SDFileInfo* file_info = malloc(sizeof(FILINFO));
FuriString* current_path = furi_string_alloc_set(path);
bool go_deeper = false;
SDError status;
while(true) {
status = f_opendir(current_dir, furi_string_get_cstr(current_path));
if(status != FR_OK) break;
while(true) {
status = f_readdir(current_dir, file_info);
if(status != FR_OK || !strlen(file_info->fname)) break;
if(file_info->fattrib & AM_DIR) {
furi_string_cat_printf(current_path, "/%s", file_info->fname);
go_deeper = true;
break;
} else {
FuriString* file_path = furi_string_alloc_printf(
"%s/%s", furi_string_get_cstr(current_path), file_info->fname);
status = f_unlink(furi_string_get_cstr(file_path));
furi_string_free(file_path);
if(status != FR_OK) break;
}
}
status = f_closedir(current_dir);
if(status != FR_OK) break;
if(go_deeper) {
go_deeper = false;
continue;
}
status = f_unlink(furi_string_get_cstr(current_path));
if(status != FR_OK) break;
if(!furi_string_equal(current_path, path)) {
size_t last_char_pos = furi_string_search_rchar(current_path, '/');
furi_assert(last_char_pos != FURI_STRING_FAILURE);
furi_string_left(current_path, last_char_pos);
} else {
break;
}
}
free(current_dir);
free(file_info);
furi_string_free(current_path);
return status == FR_OK;
}
FS_Error sd_unmount_card(StorageData* storage) {
SDData* sd_data = storage->data;
SDError error;
@ -112,21 +173,32 @@ FS_Error sd_mount_card(StorageData* storage, bool notify) {
if(storage->status != StorageStatusOK) {
FURI_LOG_E(TAG, "sd init error: %s", storage_data_status_text(storage));
if(notify) {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
sd_notify_error(notification);
furi_record_close(RECORD_NOTIFICATION);
}
error = FSE_INTERNAL;
} else {
FURI_LOG_I(TAG, "card mounted");
if(notify) {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
sd_notify_success(notification);
furi_record_close(RECORD_NOTIFICATION);
}
#ifndef FURI_RAM_EXEC
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStorageFormatInternal)) {
FURI_LOG_I(TAG, "deleting internal storage directory");
error = sd_remove_recursive(STORAGE_INTERNAL_DIR_NAME) ? FSE_OK : FSE_INTERNAL;
} else {
error = FSE_OK;
}
#else
UNUSED(sd_remove_recursive);
error = FSE_OK;
#endif
}
if(notify) {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
if(error != FSE_OK) {
sd_notify_error(notification);
} else {
sd_notify_success(notification);
}
furi_record_close(RECORD_NOTIFICATION);
}
return error;
@ -654,4 +726,8 @@ void storage_ext_init(StorageData* storage) {
// do not notify on first launch, notifications app is waiting for our thread to read settings
storage_ext_tick_internal(storage, false);
#ifndef FURI_RAM_EXEC
// always reset the flag to prevent accidental wipe on SD card insertion
furi_hal_rtc_reset_flag(FuriHalRtcFlagStorageFormatInternal);
#endif
}

View File

@ -1,9 +1,12 @@
#include <furi.h>
#include <dialogs/dialogs.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/view_holder.h>
#include <gui/modules/empty_screen.h>
#include <dialogs/dialogs.h>
#include <assets_icons.h>
#include <furi_hal_version.h>
#include <furi_hal_region.h>
#include <furi_hal_bt.h>
@ -204,7 +207,8 @@ const AboutDialogScreen about_screens[] = {
about_screen_cert_taiwan,
about_screen_cert_mexico,
about_screen_hw_version,
about_screen_fw_version};
about_screen_fw_version,
};
int32_t about_settings_app(void* p) {
UNUSED(p);
@ -212,18 +216,15 @@ int32_t about_settings_app(void* p) {
DialogMessage* message = dialog_message_alloc();
Gui* gui = furi_record_open(RECORD_GUI);
ViewDispatcher* view_dispatcher = view_dispatcher_alloc();
ViewHolder* view_holder = view_holder_alloc();
EmptyScreen* empty_screen = empty_screen_alloc();
const uint32_t empty_screen_index = 0;
size_t screen_index = 0;
DialogMessageButton screen_result;
// draw empty screen to prevent menu flickering
view_dispatcher_add_view(
view_dispatcher, empty_screen_index, empty_screen_get_view(empty_screen));
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
view_dispatcher_switch_to_view(view_dispatcher, empty_screen_index);
view_holder_attach_to_gui(view_holder, gui);
view_holder_set_view(view_holder, empty_screen_get_view(empty_screen));
while(1) {
if(screen_index >= COUNT_OF(about_screens) - 1) {
@ -258,8 +259,8 @@ int32_t about_settings_app(void* p) {
dialog_message_free(message);
furi_record_close(RECORD_DIALOGS);
view_dispatcher_remove_view(view_dispatcher, empty_screen_index);
view_dispatcher_free(view_dispatcher);
view_holder_set_view(view_holder, NULL);
view_holder_free(view_holder);
empty_screen_free(empty_screen);
furi_record_close(RECORD_GUI);

Some files were not shown because too many files have changed in this diff Show More