mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-21 00:58:10 -07:00
[FL-3841] FuriEventLoop Pt.2 (#3703)
* Abstract primitive type from main logic in FuriEventLoop * Remove message_queue_i.h * Add stream buffer support for event loop * Add semaphore support for event loop * Add temporary unit test workaround * Make the linter happy * Add mutex support for event loop * Implement event subscription and unsubscription while the event loop is running * Implement edge events * Fix leftover logical errors * Add event loop timer example application * Implement flag-based edge trigger and one-shot mode * Add event loop mutex example application * Only notify the event loop if stream buffer is at or above its trigger level * Reformat comments * Add event loop stream buffer example application * Add event loop multiple elements example application * Improve event loop flag names * Remove redundant signal handler as it is already handled by the event loop * Refactor Power service, improve ViewHolder * Use ViewHolder instead of ViewDispatcher in About app * Enable ViewDispatcher queue on construction, deprecate view_dispatcher_enable_queue() * Remove all invocations of view_dispatcher_enable_queue() * Remove app-scened-template * Remove missing library from target.json * Port Accessor app to ViewHolder * Make the linter happy * Add example_view_holder application, update ViewHolder docs * Add example_view_dispatcher application, update ViewDispatcher docs * Replace FuriSemaphore with FuriApiLock, remove workaround delay * Fix logical error * Fix another logical error * Use the sources directive to speed up compilation * Use constant define macro * Improve FuriEventLoop documentation * Improve FuriEventLoop documentation once more * Bump API Version * Gui: remove redundant checks from ViewDispatcher * Gui: remove dead ifs from ViewDispatcher Co-authored-by: Silent <CookiePLMonster@users.noreply.github.com> Co-authored-by: hedger <hedger@users.noreply.github.com> Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
App(
|
||||
appid="example_event_loop_timer",
|
||||
name="Example: Event Loop Timer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
sources=["example_event_loop_timer.c"],
|
||||
entry_point="example_event_loop_timer_app",
|
||||
fap_category="Examples",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="example_event_loop_mutex",
|
||||
name="Example: Event Loop Mutex",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
sources=["example_event_loop_mutex.c"],
|
||||
entry_point="example_event_loop_mutex_app",
|
||||
fap_category="Examples",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="example_event_loop_stream_buffer",
|
||||
name="Example: Event Loop Stream Buffer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
sources=["example_event_loop_stream_buffer.c"],
|
||||
entry_point="example_event_loop_stream_buffer_app",
|
||||
fap_category="Examples",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="example_event_loop_multi",
|
||||
name="Example: Event Loop Multi",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
sources=["example_event_loop_multi.c"],
|
||||
entry_point="example_event_loop_multi_app",
|
||||
requires=["gui"],
|
||||
fap_category="Examples",
|
||||
)
|
||||
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* @file example_event_loop_multi.c
|
||||
* @brief Example application that demonstrates multiple primitives used with two FuriEventLoop instances.
|
||||
*
|
||||
* This application simulates a complex use case of having two concurrent event loops (each one executing in
|
||||
* its own thread) using a stream buffer for communication and additional timers and message passing to handle
|
||||
* the keypad input. Additionally, it shows how to use thread signals to stop an event loop in another thread.
|
||||
* The GUI functionality is there only for the purpose of exclusive access to the input events.
|
||||
*
|
||||
* The application's functionality consists of the following:
|
||||
* - Print keypad key names and types when pressed,
|
||||
* - If the Back key is long-pressed, a countdown starts upon completion of which the app exits,
|
||||
* - The countdown can be cancelled by long-pressing the Ok button, it also resets the counter,
|
||||
* - Blocks of random data are periodically generated in a separate thread,
|
||||
* - When ready, the main application thread gets notified and prints the data.
|
||||
*/
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_port.h>
|
||||
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "ExampleEventLoopMulti"
|
||||
|
||||
#define COUNTDOWN_START_VALUE (5UL)
|
||||
#define COUNTDOWN_INTERVAL_MS (1000UL)
|
||||
#define WORKER_DATA_INTERVAL_MS (1500UL)
|
||||
|
||||
#define INPUT_QUEUE_SIZE (8)
|
||||
#define STREAM_BUFFER_SIZE (16)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriEventLoopTimer* timer;
|
||||
FuriStreamBuffer* stream_buffer;
|
||||
} EventLoopMultiAppWorker;
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewPort* view_port;
|
||||
FuriThread* worker_thread;
|
||||
FuriEventLoop* event_loop;
|
||||
FuriMessageQueue* input_queue;
|
||||
FuriEventLoopTimer* exit_timer;
|
||||
FuriStreamBuffer* stream_buffer;
|
||||
uint32_t exit_countdown_value;
|
||||
} EventLoopMultiApp;
|
||||
|
||||
/*
|
||||
* Worker functions
|
||||
*/
|
||||
|
||||
// This function is executed each time the data is taken out of the stream buffer. It is used to restart the worker timer.
|
||||
static bool
|
||||
event_loop_multi_app_stream_buffer_worker_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiAppWorker* worker = context;
|
||||
|
||||
furi_assert(object == worker->stream_buffer);
|
||||
|
||||
FURI_LOG_I(TAG, "Data was removed from buffer");
|
||||
// Restart the timer to generate another block of random data.
|
||||
furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is executed when the worker timer expires. The timer will NOT restart automatically
|
||||
// since it is of one-shot type.
|
||||
static void event_loop_multi_app_worker_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiAppWorker* worker = context;
|
||||
|
||||
// Generate a block of random data.
|
||||
uint8_t data[STREAM_BUFFER_SIZE];
|
||||
furi_hal_random_fill_buf(data, sizeof(data));
|
||||
// Put the generated data in the stream buffer.
|
||||
// IMPORTANT: No waiting in the event handlers!
|
||||
furi_check(
|
||||
furi_stream_buffer_send(worker->stream_buffer, &data, sizeof(data), 0) == sizeof(data));
|
||||
}
|
||||
|
||||
static EventLoopMultiAppWorker*
|
||||
event_loop_multi_app_worker_alloc(FuriStreamBuffer* stream_buffer) {
|
||||
EventLoopMultiAppWorker* worker = malloc(sizeof(EventLoopMultiAppWorker));
|
||||
// Create the worker event loop.
|
||||
worker->event_loop = furi_event_loop_alloc();
|
||||
// Create the timer governing the data generation.
|
||||
// It is of one-shot type, i.e. it will not restart automatically upon expiration.
|
||||
worker->timer = furi_event_loop_timer_alloc(
|
||||
worker->event_loop,
|
||||
event_loop_multi_app_worker_timer_callback,
|
||||
FuriEventLoopTimerTypeOnce,
|
||||
worker);
|
||||
|
||||
// Using the same stream buffer as the main thread (it was already created beforehand).
|
||||
worker->stream_buffer = stream_buffer;
|
||||
// Notify the worker event loop about data being taken out of the stream buffer.
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
worker->event_loop,
|
||||
worker->stream_buffer,
|
||||
FuriEventLoopEventOut | FuriEventLoopEventFlagEdge,
|
||||
event_loop_multi_app_stream_buffer_worker_callback,
|
||||
worker);
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
||||
static void event_loop_multi_app_worker_free(EventLoopMultiAppWorker* worker) {
|
||||
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_unsubscribe(worker->event_loop, worker->stream_buffer);
|
||||
// IMPORTANT: All timers MUST be deleted before deleting the associated event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_timer_free(worker->timer);
|
||||
// Now it is okay to delete the event loop.
|
||||
furi_event_loop_free(worker->event_loop);
|
||||
|
||||
free(worker);
|
||||
}
|
||||
|
||||
static void event_loop_multi_app_worker_run(EventLoopMultiAppWorker* worker) {
|
||||
furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
|
||||
furi_event_loop_run(worker->event_loop);
|
||||
}
|
||||
|
||||
// This function is the worker thread body and (obviously) is executed in the worker thread.
|
||||
static int32_t event_loop_multi_app_worker_thread(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
|
||||
// Because an event loop is used, it MUST be created in the thread it will be run in.
|
||||
// Therefore, the worker creation and deletion is handled in the worker thread.
|
||||
EventLoopMultiAppWorker* worker = event_loop_multi_app_worker_alloc(app->stream_buffer);
|
||||
event_loop_multi_app_worker_run(worker);
|
||||
event_loop_multi_app_worker_free(worker);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Main application functions
|
||||
*/
|
||||
|
||||
// This function is executed in the GUI context each time an input event occurs (e.g. the user pressed a key)
|
||||
static void event_loop_multi_app_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
// Pass the event to the the application's input queue
|
||||
furi_check(furi_message_queue_put(app->input_queue, event, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
// This function is executed each time new data is available in the stream buffer.
|
||||
static bool
|
||||
event_loop_multi_app_stream_buffer_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
|
||||
furi_assert(object == app->stream_buffer);
|
||||
// Get the data from the stream buffer
|
||||
uint8_t data[STREAM_BUFFER_SIZE];
|
||||
// IMPORTANT: No waiting in the event handlers!
|
||||
furi_check(
|
||||
furi_stream_buffer_receive(app->stream_buffer, &data, sizeof(data), 0) == sizeof(data));
|
||||
|
||||
// Format the data for printing and print it to the debug output.
|
||||
FuriString* tmp_str = furi_string_alloc();
|
||||
for(uint32_t i = 0; i < sizeof(data); ++i) {
|
||||
furi_string_cat_printf(tmp_str, "%02X ", data[i]);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
|
||||
furi_string_free(tmp_str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is executed each time a new message is inserted in the input queue.
|
||||
static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
|
||||
furi_assert(object == app->input_queue);
|
||||
|
||||
InputEvent event;
|
||||
// IMPORTANT: No waiting in the event handlers!
|
||||
furi_check(furi_message_queue_get(app->input_queue, &event, 0) == FuriStatusOk);
|
||||
|
||||
if(event.type == InputTypeLong) {
|
||||
// The user has long-pressed the Back key, try starting the countdown.
|
||||
if(event.key == InputKeyBack) {
|
||||
if(!furi_event_loop_timer_is_running(app->exit_timer)) {
|
||||
// Actually start the countdown
|
||||
FURI_LOG_I(TAG, "Starting exit countdown!");
|
||||
furi_event_loop_timer_start(app->exit_timer, COUNTDOWN_INTERVAL_MS);
|
||||
|
||||
} else {
|
||||
// The countdown is already in progress, print a warning message
|
||||
FURI_LOG_W(TAG, "Countdown has already been started");
|
||||
}
|
||||
|
||||
// The user has long-pressed the Ok key, try stopping the countdown.
|
||||
} else if(event.key == InputKeyOk) {
|
||||
if(furi_event_loop_timer_is_running(app->exit_timer)) {
|
||||
// Actually cancel the countdown
|
||||
FURI_LOG_I(TAG, "Exit countdown cancelled!");
|
||||
app->exit_countdown_value = COUNTDOWN_START_VALUE;
|
||||
furi_event_loop_timer_stop(app->exit_timer);
|
||||
|
||||
} else {
|
||||
// The countdown is not running, print a warning message
|
||||
FURI_LOG_W(TAG, "Countdown has not been started yet");
|
||||
}
|
||||
|
||||
} else {
|
||||
// Not a Back or Ok key, just print its name.
|
||||
FURI_LOG_I(TAG, "Long press: %s", input_get_key_name(event.key));
|
||||
}
|
||||
|
||||
} else if(event.type == InputTypeShort) {
|
||||
// Not a long press, just print the key's name.
|
||||
FURI_LOG_I(TAG, "Short press: %s", input_get_key_name(event.key));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is executed each time the countdown timer expires.
|
||||
static void event_loop_multi_app_exit_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
|
||||
FURI_LOG_I(TAG, "Exiting in %lu ...", app->exit_countdown_value);
|
||||
|
||||
// If the coundown value has reached 0, exit the application
|
||||
if(app->exit_countdown_value == 0) {
|
||||
FURI_LOG_I(TAG, "Exiting NOW!");
|
||||
|
||||
// Send a signal to the worker thread to exit.
|
||||
// A signal handler that handles FuriSignalExit is already set by default.
|
||||
furi_thread_signal(app->worker_thread, FuriSignalExit, NULL);
|
||||
// Request the application event loop to stop.
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
|
||||
// Otherwise just decrement it and wait for the next time the timer expires.
|
||||
} else {
|
||||
app->exit_countdown_value -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static EventLoopMultiApp* event_loop_multi_app_alloc(void) {
|
||||
EventLoopMultiApp* app = malloc(sizeof(EventLoopMultiApp));
|
||||
// Create event loop instances.
|
||||
app->event_loop = furi_event_loop_alloc();
|
||||
|
||||
// Create a worker thread instance. The worker event loop will execute inside it.
|
||||
app->worker_thread = furi_thread_alloc_ex(
|
||||
"EventLoopMultiWorker", 1024, event_loop_multi_app_worker_thread, app);
|
||||
// Create a message queue to receive the input events.
|
||||
app->input_queue = furi_message_queue_alloc(INPUT_QUEUE_SIZE, sizeof(InputEvent));
|
||||
// Create a stream buffer to receive the generated data.
|
||||
app->stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_SIZE);
|
||||
// Create a timer to run the countdown.
|
||||
app->exit_timer = furi_event_loop_timer_alloc(
|
||||
app->event_loop,
|
||||
event_loop_multi_app_exit_timer_callback,
|
||||
FuriEventLoopTimerTypePeriodic,
|
||||
app);
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->view_port = view_port_alloc();
|
||||
// Start the countdown from this value
|
||||
app->exit_countdown_value = COUNTDOWN_START_VALUE;
|
||||
// Gain exclusive access to the input events
|
||||
view_port_input_callback_set(app->view_port, event_loop_multi_app_input_callback, app);
|
||||
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
|
||||
// Notify the event loop about incoming messages in the queue
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
app->event_loop,
|
||||
app->input_queue,
|
||||
FuriEventLoopEventIn,
|
||||
event_loop_multi_app_input_queue_callback,
|
||||
app);
|
||||
// Notify the event loop about new data in the stream buffer
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
app->event_loop,
|
||||
app->stream_buffer,
|
||||
FuriEventLoopEventIn | FuriEventLoopEventFlagEdge,
|
||||
event_loop_multi_app_stream_buffer_callback,
|
||||
app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void event_loop_multi_app_free(EventLoopMultiApp* app) {
|
||||
gui_remove_view_port(app->gui, app->view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_unsubscribe(app->event_loop, app->input_queue);
|
||||
furi_event_loop_unsubscribe(app->event_loop, app->stream_buffer);
|
||||
// Delete all instances
|
||||
view_port_free(app->view_port);
|
||||
furi_message_queue_free(app->input_queue);
|
||||
furi_stream_buffer_free(app->stream_buffer);
|
||||
// IMPORTANT: All timers MUST be deleted before deleting the associated event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_timer_free(app->exit_timer);
|
||||
furi_thread_free(app->worker_thread);
|
||||
furi_event_loop_free(app->event_loop);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void event_loop_multi_app_run(EventLoopMultiApp* app) {
|
||||
FURI_LOG_I(TAG, "Press keys to see them printed here.");
|
||||
FURI_LOG_I(TAG, "Long press \"Back\" to exit after %lu seconds.", COUNTDOWN_START_VALUE);
|
||||
FURI_LOG_I(TAG, "Long press \"Ok\" to cancel the countdown.");
|
||||
|
||||
// Start the worker thread
|
||||
furi_thread_start(app->worker_thread);
|
||||
// Run the application event loop. This call will block until the application is about to exit.
|
||||
furi_event_loop_run(app->event_loop);
|
||||
// Wait for the worker thread to finish.
|
||||
furi_thread_join(app->worker_thread);
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
* vvv START HERE vvv
|
||||
*
|
||||
* The application's entry point - referenced in application.fam
|
||||
*******************************************************************/
|
||||
int32_t example_event_loop_multi_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
EventLoopMultiApp* app = event_loop_multi_app_alloc();
|
||||
event_loop_multi_app_run(app);
|
||||
event_loop_multi_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* @file example_event_loop_mutex.c
|
||||
* @brief Example application that demonstrates the FuriEventLoop and FuriMutex integration.
|
||||
*
|
||||
* This application simulates a use case where a time-consuming blocking operation is executed
|
||||
* in a separate thread and a mutex is being used for synchronization. The application runs 10 iterations
|
||||
* of the above mentioned simulated work and prints the results to the debug output each time, then exits.
|
||||
*/
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "ExampleEventLoopMutex"
|
||||
|
||||
#define WORKER_ITERATION_COUNT (10)
|
||||
// We are interested in IN events (for the mutex, that means that the mutex has been released),
|
||||
// using edge trigger mode (reacting only to changes in mutex state) and
|
||||
// employing one-shot mode to automatically unsubscribe before the event is processed.
|
||||
#define MUTEX_EVENT_AND_FLAGS \
|
||||
(FuriEventLoopEventIn | FuriEventLoopEventFlagEdge | FuriEventLoopEventFlagOnce)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriThread* worker_thread;
|
||||
FuriMutex* worker_mutex;
|
||||
uint8_t worker_result;
|
||||
} EventLoopMutexApp;
|
||||
|
||||
// This funciton is being run in a separate thread to simulate lenghty blocking operations
|
||||
static int32_t event_loop_mutex_app_worker_thread(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMutexApp* app = context;
|
||||
|
||||
FURI_LOG_I(TAG, "Worker thread started");
|
||||
|
||||
// Run 10 iterations of simulated work
|
||||
for(uint32_t i = 0; i < WORKER_ITERATION_COUNT; ++i) {
|
||||
FURI_LOG_I(TAG, "Doing work ...");
|
||||
// Take the mutex so that no-one can access the worker_result variable
|
||||
furi_check(furi_mutex_acquire(app->worker_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
// Simulate a blocking operation with a random delay between 900 and 1100 ms
|
||||
const uint32_t work_time_ms = 900 + furi_hal_random_get() % 200;
|
||||
furi_delay_ms(work_time_ms);
|
||||
// Simulate a result with a random number between 0 and 255
|
||||
app->worker_result = furi_hal_random_get() % 0xFF;
|
||||
|
||||
FURI_LOG_I(TAG, "Work done in %lu ms", work_time_ms);
|
||||
// Release the mutex, which will notify the event loop that the result is ready
|
||||
furi_check(furi_mutex_release(app->worker_mutex) == FuriStatusOk);
|
||||
// Return control to the scheduler so that the event loop can take the mutex in its turn
|
||||
furi_thread_yield();
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "All work done, worker thread out!");
|
||||
// Request the event loop to stop
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This function is being run each time when the mutex gets released
|
||||
static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
EventLoopMutexApp* app = context;
|
||||
furi_assert(object == app->worker_mutex);
|
||||
|
||||
// Take the mutex so that no-one can access the worker_result variable
|
||||
// IMPORTANT: the wait time MUST be 0, i.e. the event loop event callbacks
|
||||
// must NOT ever block. If it is possible that the mutex will be taken by
|
||||
// others, then the event callback code must take it into account.
|
||||
furi_check(furi_mutex_acquire(app->worker_mutex, 0) == FuriStatusOk);
|
||||
// Access the worker_result variable and print it.
|
||||
FURI_LOG_I(TAG, "Result available! Value: %u", app->worker_result);
|
||||
// Release the mutex, enabling the worker thread to continue when it's ready
|
||||
furi_check(furi_mutex_release(app->worker_mutex) == FuriStatusOk);
|
||||
// Subscribe for the mutex release events again, since we were unsubscribed automatically
|
||||
// before processing the event.
|
||||
furi_event_loop_subscribe_mutex(
|
||||
app->event_loop,
|
||||
app->worker_mutex,
|
||||
MUTEX_EVENT_AND_FLAGS,
|
||||
event_loop_mutex_app_event_callback,
|
||||
app);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static EventLoopMutexApp* event_loop_mutex_app_alloc(void) {
|
||||
EventLoopMutexApp* app = malloc(sizeof(EventLoopMutexApp));
|
||||
|
||||
// Create an event loop instance.
|
||||
app->event_loop = furi_event_loop_alloc();
|
||||
// Create a worker thread instance.
|
||||
app->worker_thread = furi_thread_alloc_ex(
|
||||
"EventLoopMutexWorker", 1024, event_loop_mutex_app_worker_thread, app);
|
||||
// Create a mutex instance.
|
||||
app->worker_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
// Subscribe for the mutex release events.
|
||||
// Note that since FuriEventLoopEventFlagOneShot is used, we will be automatically unsubscribed
|
||||
// from events before entering the event processing callback. This is necessary in order to not
|
||||
// trigger on events caused by releasing the mutex in the callback.
|
||||
furi_event_loop_subscribe_mutex(
|
||||
app->event_loop,
|
||||
app->worker_mutex,
|
||||
MUTEX_EVENT_AND_FLAGS,
|
||||
event_loop_mutex_app_event_callback,
|
||||
app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void event_loop_mutex_app_free(EventLoopMutexApp* app) {
|
||||
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_unsubscribe(app->event_loop, app->worker_mutex);
|
||||
// Delete all instances
|
||||
furi_thread_free(app->worker_thread);
|
||||
furi_mutex_free(app->worker_mutex);
|
||||
furi_event_loop_free(app->event_loop);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void event_loop_mutex_app_run(EventLoopMutexApp* app) {
|
||||
furi_thread_start(app->worker_thread);
|
||||
furi_event_loop_run(app->event_loop);
|
||||
furi_thread_join(app->worker_thread);
|
||||
}
|
||||
|
||||
// The application's entry point - referenced in application.fam
|
||||
int32_t example_event_loop_mutex_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
EventLoopMutexApp* app = event_loop_mutex_app_alloc();
|
||||
event_loop_mutex_app_run(app);
|
||||
event_loop_mutex_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* @file example_event_loop_stream_buffer.c
|
||||
* @brief Example application that demonstrates the FuriEventLoop and FuriStreamBuffer integration.
|
||||
*
|
||||
* This application simulates a use case where some data data stream comes from a separate thread (or hardware)
|
||||
* and a stream buffer is used to act as an intermediate buffer. The worker thread produces 10 iterations of 32
|
||||
* bytes of simulated data, and each time when the buffer is half-filled, the data is taken out of it and printed
|
||||
* to the debug output. After completing all iterations, the application exits.
|
||||
*/
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "ExampleEventLoopStreamBuffer"
|
||||
|
||||
#define WORKER_ITERATION_COUNT (10)
|
||||
|
||||
#define STREAM_BUFFER_SIZE (32)
|
||||
#define STREAM_BUFFER_TRIG_LEVEL (STREAM_BUFFER_SIZE / 2)
|
||||
#define STREAM_BUFFER_EVENT_AND_FLAGS (FuriEventLoopEventIn | FuriEventLoopEventFlagEdge)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriThread* worker_thread;
|
||||
FuriStreamBuffer* stream_buffer;
|
||||
} EventLoopStreamBufferApp;
|
||||
|
||||
// This funciton is being run in a separate thread to simulate data coming from a producer thread or some device.
|
||||
static int32_t event_loop_stream_buffer_app_worker_thread(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopStreamBufferApp* app = context;
|
||||
|
||||
FURI_LOG_I(TAG, "Worker thread started");
|
||||
|
||||
for(uint32_t i = 0; i < WORKER_ITERATION_COUNT; ++i) {
|
||||
// Produce 32 bytes of simulated data.
|
||||
for(uint32_t j = 0; j < STREAM_BUFFER_SIZE; ++j) {
|
||||
// Simulate incoming data by generating a random byte.
|
||||
uint8_t data = furi_hal_random_get() % 0xFF;
|
||||
// Put the byte in the buffer. Depending on the use case, it may or may be not acceptable
|
||||
// to wait for free space to become available.
|
||||
furi_check(
|
||||
furi_stream_buffer_send(app->stream_buffer, &data, 1, FuriWaitForever) == 1);
|
||||
// Delay between 30 and 50 ms to slow down the output for clarity.
|
||||
furi_delay_ms(30 + furi_hal_random_get() % 20);
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "All work done, worker thread out!");
|
||||
// Request the event loop to stop
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This function is being run each time when the number of bytes in the buffer is above its trigger level.
|
||||
static bool
|
||||
event_loop_stream_buffer_app_event_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopStreamBufferApp* app = context;
|
||||
|
||||
furi_assert(object == app->stream_buffer);
|
||||
|
||||
// Temporary buffer that can hold at most half of the stream buffer's capacity.
|
||||
uint8_t data[STREAM_BUFFER_TRIG_LEVEL];
|
||||
// Receive the data. It is guaranteed that the amount of data in the buffer will be equal to
|
||||
// or greater than the trigger level, therefore, no waiting delay is necessary.
|
||||
furi_check(
|
||||
furi_stream_buffer_receive(app->stream_buffer, data, sizeof(data), 0) == sizeof(data));
|
||||
|
||||
// Format the data for printing and print it to the debug output.
|
||||
FuriString* tmp_str = furi_string_alloc();
|
||||
for(uint32_t i = 0; i < sizeof(data); ++i) {
|
||||
furi_string_cat_printf(tmp_str, "%02X ", data[i]);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
|
||||
furi_string_free(tmp_str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static EventLoopStreamBufferApp* event_loop_stream_buffer_app_alloc(void) {
|
||||
EventLoopStreamBufferApp* app = malloc(sizeof(EventLoopStreamBufferApp));
|
||||
|
||||
// Create an event loop instance.
|
||||
app->event_loop = furi_event_loop_alloc();
|
||||
// Create a worker thread instance.
|
||||
app->worker_thread = furi_thread_alloc_ex(
|
||||
"EventLoopStreamBufferWorker", 1024, event_loop_stream_buffer_app_worker_thread, app);
|
||||
// Create a stream_buffer instance.
|
||||
app->stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_TRIG_LEVEL);
|
||||
// Subscribe for the stream buffer IN events in edge triggered mode.
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
app->event_loop,
|
||||
app->stream_buffer,
|
||||
STREAM_BUFFER_EVENT_AND_FLAGS,
|
||||
event_loop_stream_buffer_app_event_callback,
|
||||
app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void event_loop_stream_buffer_app_free(EventLoopStreamBufferApp* app) {
|
||||
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_unsubscribe(app->event_loop, app->stream_buffer);
|
||||
// Delete all instances
|
||||
furi_thread_free(app->worker_thread);
|
||||
furi_stream_buffer_free(app->stream_buffer);
|
||||
furi_event_loop_free(app->event_loop);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void event_loop_stream_buffer_app_run(EventLoopStreamBufferApp* app) {
|
||||
furi_thread_start(app->worker_thread);
|
||||
furi_event_loop_run(app->event_loop);
|
||||
furi_thread_join(app->worker_thread);
|
||||
}
|
||||
|
||||
// The application's entry point - referenced in application.fam
|
||||
int32_t example_event_loop_stream_buffer_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
EventLoopStreamBufferApp* app = event_loop_stream_buffer_app_alloc();
|
||||
event_loop_stream_buffer_app_run(app);
|
||||
event_loop_stream_buffer_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @file example_event_loop_timer.c
|
||||
* @brief Example application that demonstrates FuriEventLoop's software timer capability.
|
||||
*
|
||||
* This application prints a countdown from 10 to 0 to the debug output and then exits.
|
||||
* Despite only one timer being used in this example for clarity, an event loop instance can have
|
||||
* an arbitrary number of independent timers of any type (periodic or one-shot).
|
||||
*
|
||||
*/
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "ExampleEventLoopTimer"
|
||||
|
||||
#define COUNTDOWN_START_VALUE (10)
|
||||
#define COUNTDOWN_INTERVAL_MS (1000)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriEventLoopTimer* timer;
|
||||
uint32_t countdown_value;
|
||||
} EventLoopTimerApp;
|
||||
|
||||
// This function is called each time the timer expires (i.e. once per 1000 ms (1s) in this example)
|
||||
static void event_loop_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopTimerApp* app = context;
|
||||
|
||||
// Print the countdown value
|
||||
FURI_LOG_I(TAG, "T-00:00:%02lu", app->countdown_value);
|
||||
|
||||
if(app->countdown_value == 0) {
|
||||
// If the countdown reached 0, print the final line and stop the event loop
|
||||
FURI_LOG_I(TAG, "Blast off to adventure!");
|
||||
// After this call, the control will be returned back to event_loop_timers_app_run()
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
|
||||
} else {
|
||||
// Decrement the countdown value
|
||||
app->countdown_value -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static EventLoopTimerApp* event_loop_timer_app_alloc(void) {
|
||||
EventLoopTimerApp* app = malloc(sizeof(EventLoopTimerApp));
|
||||
|
||||
// Create an event loop instance.
|
||||
app->event_loop = furi_event_loop_alloc();
|
||||
// Create a software timer instance.
|
||||
// The timer is bound to the event loop instance and will execute in its context.
|
||||
// Here, the timer type is periodic, i.e. it will restart automatically after expiring.
|
||||
app->timer = furi_event_loop_timer_alloc(
|
||||
app->event_loop, event_loop_timer_callback, FuriEventLoopTimerTypePeriodic, app);
|
||||
// The countdown value will be tracked in this variable.
|
||||
app->countdown_value = COUNTDOWN_START_VALUE;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void event_loop_timer_app_free(EventLoopTimerApp* app) {
|
||||
// IMPORTANT: All event loop timers MUST be deleted BEFORE deleting the event loop itself.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_timer_free(app->timer);
|
||||
// With all timers deleted, it's safe to delete the event loop.
|
||||
furi_event_loop_free(app->event_loop);
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void event_loop_timer_app_run(EventLoopTimerApp* app) {
|
||||
FURI_LOG_I(TAG, "All systems go! Prepare for countdown!");
|
||||
|
||||
// Timers can be started either before the event loop is run, or in any
|
||||
// callback function called by a running event loop.
|
||||
furi_event_loop_timer_start(app->timer, COUNTDOWN_INTERVAL_MS);
|
||||
// This call will block until furi_event_loop_stop() is called.
|
||||
furi_event_loop_run(app->event_loop);
|
||||
}
|
||||
|
||||
// The application's entry point - referenced in application.fam
|
||||
int32_t example_event_loop_timer_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
EventLoopTimerApp* app = event_loop_timer_app_alloc();
|
||||
event_loop_timer_app_run(app);
|
||||
event_loop_timer_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user