[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:
MX
2024-08-10 14:32:27 +03:00
parent 1e5dd001fe
commit bf6c6c231f
104 changed files with 2099 additions and 1757 deletions
@@ -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;
}