[FL-3846] Event Loop Timers (#3721)

* Implement POC event loop tmers (not all edge cases are handled)
* Use a separate ready list to allow for (re)starting and stopping of timers from callback
* Improve the test application
* Improve timer API and test application
* Improve timeout calculation logic
* Improve timer API, update documentation
* Fix API usage error
* Update doxygen comments
* Revert the old (correct) check
* Improve function naming
* Check whether a timer was on the expired list before processing it
* Implement tick callback
* Add critical sections to improve timer consistency
* Simplify event loop timer API
* Remove redundant search
* Refactor timer logic, use message queue
* Simplify FuriEventLoopTimer API
* Improve event loop timer logic
* Update the f18 target
* Remove superfluous clears
* Correct f18 api symbols
* Fix doxygen comments
* Update .pvsconfig
* Use a double push list instead of deque
* Update .pvsconfig
* Add pending callback functionality
* Restore unprocessed flags when applicable
* Refactor Dolphin app to use FuriEventLoop
* Improve naming
* Update naming some more
* Fix a typo Co-authored-by: Silent <CookiePLMonster@users.noreply.github.com>
* Fix wait time in example
* Bump API version
* Debug: multiple of 25 timings in event loop blink test
* Separate FuriEventLoopTimer to its own set of files
* Improve start time calculation for periodic timers
* Do not use dynamic allocations for timer requests
* Split the tick functionality in separate files, rearrange code
* Improve timer queue handling
* Properly reset GPIO pins in the test app
* Properly initialise GPIO pins in the test app too
* Furi: variable naming in event loop
* Furi: fix spelling in event loop

Co-authored-by: あく <alleteam@gmail.com>
Co-authored-by: Silent <CookiePLMonster@users.noreply.github.com>
This commit is contained in:
Georgii Surkov 2024-07-02 15:09:50 +03:00 committed by GitHub
parent bf90843f25
commit 139660d206
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1087 additions and 256 deletions

View file

@ -3,10 +3,12 @@
//-V:M_EACH:1048,1044
//-V:ARRAY_DEF:760,747,568,776,729,712,654,1103
//-V:LIST_DEF:760,747,568,712,729,654,776,1103
//-V:LIST_DUAL_PUSH_DEF:524,760,774
//-V:BPTREE_DEF2:779,1086,557,773,512
//-V:DICT_DEF2:779,524,776,760,1044,1001,729,590,568,747,685,1103
//-V:ALGO_DEF:1048,747,1044
//-V:TUPLE_DEF2:524,590,1001,760
//-V:DEQUE_DEF:658,747,760
# Non-severe malloc/null pointer deref warnings
//-V::522:2,3

View file

@ -0,0 +1,10 @@
App(
appid="event_loop_blink_test",
name="Event Loop Blink Test",
apptype=FlipperAppType.DEBUG,
entry_point="event_loop_blink_test_app",
requires=["input"],
stack_size=1 * 1024,
order=20,
fap_category="Debug",
)

View file

@ -0,0 +1,169 @@
#include <furi.h>
#include <furi_hal_resources.h>
#include <gui/gui.h>
#include <gui/elements.h>
#include <gui/view_port.h>
#include <input/input.h>
#define TAG "EventLoopBlinkTest"
#define TIMER_COUNT (6U)
typedef struct {
FuriEventLoop* event_loop;
FuriMessageQueue* input_queue;
FuriEventLoopTimer* timers[TIMER_COUNT];
} EventLoopBlinkTestApp;
static const GpioPin* blink_gpio_pins[] = {
&gpio_ext_pa7,
&gpio_ext_pa6,
&gpio_ext_pa4,
&gpio_ext_pb3,
&gpio_ext_pb2,
&gpio_ext_pc3,
};
static_assert(COUNT_OF(blink_gpio_pins) == TIMER_COUNT);
static const uint32_t timer_intervals[] = {
25,
50,
100,
200,
400,
800,
};
static_assert(COUNT_OF(timer_intervals) == TIMER_COUNT);
static void blink_gpio_init(void) {
for(size_t i = 0; i < TIMER_COUNT; ++i) {
furi_hal_gpio_init_simple(blink_gpio_pins[i], GpioModeOutputPushPull);
furi_hal_gpio_write(blink_gpio_pins[i], false);
}
furi_hal_gpio_init_simple(&gpio_ext_pc0, GpioModeOutputPushPull);
furi_hal_gpio_write(&gpio_ext_pc0, false);
}
static void blink_gpio_deinit(void) {
for(size_t i = 0; i < TIMER_COUNT; ++i) {
furi_hal_gpio_write(blink_gpio_pins[i], false);
furi_hal_gpio_init_simple(blink_gpio_pins[i], GpioModeAnalog);
}
furi_hal_gpio_write(&gpio_ext_pc0, false);
furi_hal_gpio_init_simple(&gpio_ext_pc0, GpioModeAnalog);
}
static void view_port_draw_callback(Canvas* canvas, void* context) {
UNUSED(context);
canvas_clear(canvas);
elements_text_box(
canvas,
0,
0,
canvas_width(canvas),
canvas_height(canvas),
AlignCenter,
AlignCenter,
"\e#Event Loop Timers Test\e#\n"
"Press buttons\n"
"to enable or disable timers\n"
"\e#Exit\e# = long press \e#Back\e#",
false);
}
static void view_port_input_callback(InputEvent* input_event, void* context) {
EventLoopBlinkTestApp* app = context;
furi_message_queue_put(app->input_queue, input_event, 0);
}
static bool input_queue_callback(FuriMessageQueue* queue, void* context) {
EventLoopBlinkTestApp* app = context;
InputEvent event;
FuriStatus status = furi_message_queue_get(queue, &event, 0);
furi_assert(status == FuriStatusOk);
if(event.type == InputTypeShort) {
const size_t timer_idx = event.key;
furi_assert(timer_idx < TIMER_COUNT);
FuriEventLoopTimer* timer = app->timers[timer_idx];
if(furi_event_loop_timer_is_running(timer)) {
furi_event_loop_timer_stop(timer);
} else {
furi_event_loop_timer_restart(timer);
}
} else if(event.type == InputTypeLong) {
if(event.key == InputKeyBack) {
furi_event_loop_stop(app->event_loop);
}
}
return true;
}
static void blink_timer_callback(void* context) {
const GpioPin* gpio = blink_gpio_pins[(size_t)context];
furi_hal_gpio_write(gpio, !furi_hal_gpio_read(gpio));
}
static void event_loop_tick_callback(void* context) {
UNUSED(context);
furi_hal_gpio_write(&gpio_ext_pc0, !furi_hal_gpio_read(&gpio_ext_pc0));
}
int32_t event_loop_blink_test_app(void* arg) {
UNUSED(arg);
blink_gpio_init();
EventLoopBlinkTestApp app;
app.event_loop = furi_event_loop_alloc();
app.input_queue = furi_message_queue_alloc(3, sizeof(InputEvent));
for(size_t i = 0; i < TIMER_COUNT; ++i) {
app.timers[i] = furi_event_loop_timer_alloc(
app.event_loop, blink_timer_callback, FuriEventLoopTimerTypePeriodic, (void*)i);
furi_event_loop_timer_start(app.timers[i], timer_intervals[i]);
}
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, view_port_draw_callback, &app);
view_port_input_callback_set(view_port, view_port_input_callback, &app);
Gui* gui = furi_record_open(RECORD_GUI);
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(
app.event_loop, app.input_queue, FuriEventLoopEventIn, input_queue_callback, &app);
furi_event_loop_run(app.event_loop);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_record_close(RECORD_GUI);
furi_event_loop_message_queue_unsubscribe(app.event_loop, app.input_queue);
furi_message_queue_free(app.input_queue);
for(size_t i = 0; i < TIMER_COUNT; ++i) {
furi_event_loop_timer_free(app.timers[i]);
}
furi_event_loop_free(app.event_loop);
blink_gpio_deinit();
return 0;
}

View file

@ -1,22 +1,48 @@
#include "dolphin.h"
#include "helpers/dolphin_state.h"
#include "dolphin_i.h"
#include <furi_hal.h>
#include <stdint.h>
#include <furi.h>
#define DOLPHIN_LOCK_EVENT_FLAG (0x1)
#define TAG "Dolphin"
#define HOURS_IN_TICKS(x) ((x) * 60 * 60 * 1000)
static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin);
#define DOLPHIN_LOCK_EVENT_FLAG (0x1)
#define EVENT_QUEUE_SIZE (8)
#define SECONDS_IN_TICKS(x) ((x) * 1000UL)
#define MINUTES_IN_TICKS(x) (SECONDS_IN_TICKS(x) * 60UL)
#define HOURS_IN_TICKS(x) (MINUTES_IN_TICKS(x) * 60UL)
#define DATE_IN_TICKS(h, m, s) (HOURS_IN_TICKS(h) + MINUTES_IN_TICKS(m) + SECONDS_IN_TICKS(s))
#define FLUSH_TIMEOUT_TICKS (SECONDS_IN_TICKS(30UL))
#ifndef DOLPHIN_DEBUG
#define BUTTHURT_INCREASE_PERIOD_TICKS (HOURS_IN_TICKS(48UL))
#define CLEAR_LIMITS_PERIOD_TICKS (HOURS_IN_TICKS(24UL))
#define CLEAR_LIMITS_UPDATE_PERIOD_TICKS (HOURS_IN_TICKS(1UL))
#else
#define BUTTHURT_INCREASE_PERIOD_TICKS (SECONDS_IN_TICKS(30UL))
#define CLEAR_LIMITS_PERIOD_TICKS (MINUTES_IN_TICKS(1))
#define CLEAR_LIMITS_UPDATE_PERIOD_TICKS (SECONDS_IN_TICKS(5UL))
#endif
#define CLEAR_LIMITS_UPDATE_THRESHOLD_TICKS (MINUTES_IN_TICKS(5UL))
#define CLEAR_LIMITS_TIME_HOURS (5UL)
#define CLEAR_LIMITS_TIME_TICKS (HOURS_IN_TICKS(CLEAR_LIMITS_TIME_HOURS))
static void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event);
static void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event);
// Public API
void dolphin_deed(DolphinDeed deed) {
Dolphin* dolphin = (Dolphin*)furi_record_open(RECORD_DOLPHIN);
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
DolphinEvent event;
event.type = DolphinEventTypeDeed;
event.deed = deed;
dolphin_event_send_async(dolphin, &event);
furi_record_close(RECORD_DOLPHIN);
}
@ -43,52 +69,75 @@ void dolphin_flush(Dolphin* dolphin) {
dolphin_event_send_wait(dolphin, &event);
}
void dolphin_butthurt_timer_callback(void* context) {
Dolphin* dolphin = context;
furi_assert(dolphin);
void dolphin_upgrade_level(Dolphin* dolphin) {
furi_check(dolphin);
DolphinEvent event;
event.type = DolphinEventTypeIncreaseButthurt;
event.type = DolphinEventTypeLevel;
dolphin_event_send_async(dolphin, &event);
}
void dolphin_flush_timer_callback(void* context) {
FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin) {
furi_check(dolphin);
return dolphin->pubsub;
}
// Private functions
static void dolphin_butthurt_timer_callback(void* context) {
Dolphin* dolphin = context;
furi_assert(dolphin);
DolphinEvent event;
event.type = DolphinEventTypeFlush;
dolphin_event_send_async(dolphin, &event);
FURI_LOG_I(TAG, "Increase butthurt");
dolphin_state_butthurted(dolphin->state);
dolphin_state_save(dolphin->state);
}
void dolphin_clear_limits_timer_callback(void* context) {
static void dolphin_flush_timer_callback(void* context) {
Dolphin* dolphin = context;
furi_assert(dolphin);
furi_timer_start(dolphin->clear_limits_timer, HOURS_IN_TICKS(24));
DolphinEvent event;
event.type = DolphinEventTypeClearLimits;
dolphin_event_send_async(dolphin, &event);
FURI_LOG_I(TAG, "Flush stats");
dolphin_state_save(dolphin->state);
}
Dolphin* dolphin_alloc(void) {
static void dolphin_clear_limits_timer_callback(void* context) {
Dolphin* dolphin = context;
furi_assert(dolphin);
FURI_LOG_I(TAG, "Clear limits");
dolphin_state_clear_limits(dolphin->state);
dolphin_state_save(dolphin->state);
}
static Dolphin* dolphin_alloc(void) {
Dolphin* dolphin = malloc(sizeof(Dolphin));
dolphin->state = dolphin_state_alloc();
dolphin->event_queue = furi_message_queue_alloc(8, sizeof(DolphinEvent));
dolphin->pubsub = furi_pubsub_alloc();
dolphin->butthurt_timer =
furi_timer_alloc(dolphin_butthurt_timer_callback, FuriTimerTypePeriodic, dolphin);
dolphin->flush_timer =
furi_timer_alloc(dolphin_flush_timer_callback, FuriTimerTypeOnce, dolphin);
dolphin->clear_limits_timer =
furi_timer_alloc(dolphin_clear_limits_timer_callback, FuriTimerTypePeriodic, dolphin);
dolphin->event_queue = furi_message_queue_alloc(EVENT_QUEUE_SIZE, sizeof(DolphinEvent));
dolphin->event_loop = furi_event_loop_alloc();
dolphin->butthurt_timer = furi_event_loop_timer_alloc(
dolphin->event_loop,
dolphin_butthurt_timer_callback,
FuriEventLoopTimerTypePeriodic,
dolphin);
dolphin->flush_timer = furi_event_loop_timer_alloc(
dolphin->event_loop, dolphin_flush_timer_callback, FuriEventLoopTimerTypeOnce, dolphin);
dolphin->clear_limits_timer = furi_event_loop_timer_alloc(
dolphin->event_loop,
dolphin_clear_limits_timer_callback,
FuriEventLoopTimerTypePeriodic,
dolphin);
return dolphin;
}
void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event) {
static void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event) {
furi_assert(dolphin);
furi_assert(event);
event->flag = NULL;
@ -96,7 +145,7 @@ void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event) {
furi_message_queue_put(dolphin->event_queue, event, FuriWaitForever) == FuriStatusOk);
}
void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event) {
static void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event) {
furi_assert(dolphin);
furi_assert(event);
@ -110,39 +159,81 @@ void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event) {
furi_event_flag_free(event->flag);
}
void dolphin_event_release(Dolphin* dolphin, DolphinEvent* event) {
UNUSED(dolphin);
static void dolphin_event_release(DolphinEvent* event) {
if(event->flag) {
furi_event_flag_set(event->flag, DOLPHIN_LOCK_EVENT_FLAG);
}
}
FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin) {
furi_check(dolphin);
return dolphin->pubsub;
}
static void dolphin_update_clear_limits_timer_period(void* context) {
furi_assert(context);
Dolphin* dolphin = context;
static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) {
furi_assert(dolphin);
uint32_t now_ticks = furi_get_tick();
uint32_t timer_expires_at = furi_timer_get_expire_time(dolphin->clear_limits_timer);
uint32_t time_to_clear_limits =
furi_event_loop_timer_get_remaining_time(dolphin->clear_limits_timer);
if((timer_expires_at - now_ticks) > HOURS_IN_TICKS(0.1)) {
if(time_to_clear_limits > CLEAR_LIMITS_UPDATE_THRESHOLD_TICKS) {
DateTime date;
furi_hal_rtc_get_datetime(&date);
uint32_t now_time_in_ms = ((date.hour * 60 + date.minute) * 60 + date.second) * 1000;
uint32_t time_to_clear_limits = 0;
if(date.hour < 5) {
time_to_clear_limits = HOURS_IN_TICKS(5) - now_time_in_ms;
const uint32_t now_time_ticks = DATE_IN_TICKS(date.hour, date.minute, date.second);
if(date.hour < CLEAR_LIMITS_TIME_HOURS) {
time_to_clear_limits = CLEAR_LIMITS_TIME_TICKS - now_time_ticks;
} else {
time_to_clear_limits = HOURS_IN_TICKS(24 + 5) - now_time_in_ms;
time_to_clear_limits =
CLEAR_LIMITS_PERIOD_TICKS + CLEAR_LIMITS_TIME_TICKS - now_time_ticks;
}
furi_timer_start(dolphin->clear_limits_timer, time_to_clear_limits);
furi_event_loop_timer_start(dolphin->clear_limits_timer, time_to_clear_limits);
}
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);
Dolphin* dolphin = context;
DolphinEvent event;
FuriStatus status = furi_message_queue_get(dolphin->event_queue, &event, 0);
furi_check(status == FuriStatusOk);
if(event.type == DolphinEventTypeDeed) {
dolphin_state_on_deed(dolphin->state, event.deed);
DolphinPubsubEvent event = DolphinPubsubEventUpdate;
furi_pubsub_publish(dolphin->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);
} else if(event.type == DolphinEventTypeStats) {
event.stats->icounter = dolphin->state->data.icounter;
event.stats->butthurt = dolphin->state->data.butthurt;
event.stats->timestamp = dolphin->state->data.timestamp;
event.stats->level = dolphin_get_level(dolphin->state->data.icounter);
event.stats->level_up_is_pending =
!dolphin_state_xp_to_levelup(dolphin->state->data.icounter);
} else if(event.type == DolphinEventTypeFlush) {
furi_event_loop_timer_start(dolphin->flush_timer, FLUSH_TIMEOUT_TICKS);
} else if(event.type == DolphinEventTypeLevel) {
dolphin_state_increase_level(dolphin->state);
furi_event_loop_timer_start(dolphin->flush_timer, FLUSH_TIMEOUT_TICKS);
} else {
furi_crash();
}
dolphin_event_release(&event);
return true;
}
// Application thread
int32_t dolphin_srv(void* p) {
UNUSED(p);
@ -157,54 +248,27 @@ int32_t dolphin_srv(void* p) {
furi_record_create(RECORD_DOLPHIN, dolphin);
dolphin_state_load(dolphin->state);
furi_timer_restart(dolphin->butthurt_timer, HOURS_IN_TICKS(2 * 24));
dolphin_update_clear_limits_timer_period(dolphin);
furi_timer_restart(dolphin->clear_limits_timer, HOURS_IN_TICKS(24));
DolphinEvent event;
while(1) {
if(furi_message_queue_get(dolphin->event_queue, &event, HOURS_IN_TICKS(1)) ==
FuriStatusOk) {
if(event.type == DolphinEventTypeDeed) {
dolphin_state_on_deed(dolphin->state, event.deed);
DolphinPubsubEvent event = DolphinPubsubEventUpdate;
furi_pubsub_publish(dolphin->pubsub, &event);
furi_timer_restart(dolphin->butthurt_timer, HOURS_IN_TICKS(2 * 24));
furi_timer_restart(dolphin->flush_timer, 30 * 1000);
} else if(event.type == DolphinEventTypeStats) {
event.stats->icounter = dolphin->state->data.icounter;
event.stats->butthurt = dolphin->state->data.butthurt;
event.stats->timestamp = dolphin->state->data.timestamp;
event.stats->level = dolphin_get_level(dolphin->state->data.icounter);
event.stats->level_up_is_pending =
!dolphin_state_xp_to_levelup(dolphin->state->data.icounter);
} else if(event.type == DolphinEventTypeFlush) {
FURI_LOG_I(TAG, "Flush stats");
dolphin_state_save(dolphin->state);
} else if(event.type == DolphinEventTypeClearLimits) {
FURI_LOG_I(TAG, "Clear limits");
dolphin_state_clear_limits(dolphin->state);
dolphin_state_save(dolphin->state);
} else if(event.type == DolphinEventTypeIncreaseButthurt) {
FURI_LOG_I(TAG, "Increase butthurt");
dolphin_state_butthurted(dolphin->state);
dolphin_state_save(dolphin->state);
}
dolphin_event_release(dolphin, &event);
} else {
/* once per hour check rtc time is not changed */
dolphin_update_clear_limits_timer_period(dolphin);
}
}
furi_event_loop_message_queue_subscribe(
dolphin->event_loop,
dolphin->event_queue,
FuriEventLoopEventIn,
dolphin_process_event,
dolphin);
furi_crash("That was unexpected");
furi_event_loop_timer_start(dolphin->butthurt_timer, BUTTHURT_INCREASE_PERIOD_TICKS);
furi_event_loop_timer_start(dolphin->clear_limits_timer, CLEAR_LIMITS_PERIOD_TICKS);
furi_event_loop_tick_set(
dolphin->event_loop,
CLEAR_LIMITS_UPDATE_PERIOD_TICKS,
dolphin_update_clear_limits_timer_period,
dolphin);
furi_event_loop_pend_callback(
dolphin->event_loop, dolphin_update_clear_limits_timer_period, dolphin);
furi_event_loop_run(dolphin->event_loop);
return 0;
}
void dolphin_upgrade_level(Dolphin* dolphin) {
furi_check(dolphin);
dolphin_state_increase_level(dolphin->state);
dolphin_flush(dolphin);
}

View file

@ -1,10 +1,9 @@
#pragma once
#include "helpers/dolphin_deed.h"
#include <gui/view.h>
#include <core/pubsub.h>
#include <stdbool.h>
#include <core/pubsub.h>
#include "helpers/dolphin_deed.h"
#ifdef __cplusplus
extern "C" {

View file

@ -1,8 +1,8 @@
#pragma once
#include <core/pubsub.h>
#include <furi.h>
#include <furi_hal.h>
#include <core/pubsub.h>
#include "dolphin.h"
#include "helpers/dolphin_state.h"
@ -11,8 +11,7 @@ typedef enum {
DolphinEventTypeDeed,
DolphinEventTypeStats,
DolphinEventTypeFlush,
DolphinEventTypeIncreaseButthurt,
DolphinEventTypeClearLimits,
DolphinEventTypeLevel,
} DolphinEventType;
typedef struct {
@ -25,20 +24,11 @@ typedef struct {
} DolphinEvent;
struct Dolphin {
// State
DolphinState* state;
// Queue
FuriMessageQueue* event_queue;
FuriPubSub* pubsub;
FuriTimer* butthurt_timer;
FuriTimer* flush_timer;
FuriTimer* clear_limits_timer;
FuriMessageQueue* event_queue;
FuriEventLoop* event_loop;
FuriEventLoopTimer* butthurt_timer;
FuriEventLoopTimer* flush_timer;
FuriEventLoopTimer* clear_limits_timer;
};
Dolphin* dolphin_alloc(void);
void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event);
void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event);
void dolphin_event_release(Dolphin* dolphin, DolphinEvent* event);

View file

@ -5,32 +5,14 @@
#include "check.h"
#include "thread.h"
#include <m-bptree.h>
#include <m-i-list.h>
#include <FreeRTOS.h>
#include <task.h>
#define TAG "FuriEventLoop"
struct FuriEventLoopItem {
// Source
FuriEventLoop* owner;
// Tracking item
const FuriEventLoopContract* contract;
void* object;
FuriEventLoopEvent event;
// Callback and context
FuriEventLoopMessageQueueCallback callback;
void* callback_context;
// Waiting list
ILIST_INTERFACE(WaitingList, struct FuriEventLoopItem);
};
ILIST_DEF(WaitingList, FuriEventLoopItem, M_POD_OPLIST)
/*
* Private functions
*/
static FuriEventLoopItem* furi_event_loop_item_alloc(
FuriEventLoop* owner,
@ -47,56 +29,17 @@ static void furi_event_loop_item_set_callback(
static void furi_event_loop_item_notify(FuriEventLoopItem* instance);
/* Event Loop RB tree */
#define FURI_EVENT_LOOP_TREE_RANK (4)
static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) {
for(; !PendingQueue_empty_p(instance->pending_queue);
PendingQueue_pop_back(NULL, instance->pending_queue)) {
const FuriEventLoopPendingQueueItem* item = PendingQueue_back(instance->pending_queue);
item->callback(item->context);
}
}
BPTREE_DEF2( // NOLINT
FuriEventLoopTree,
FURI_EVENT_LOOP_TREE_RANK,
void*, /* pointer to object we track */
M_PTR_OPLIST,
FuriEventLoopItem*, /* pointer to the FuriEventLoopItem */
M_PTR_OPLIST)
#define M_OPL_FuriEventLoopTree_t() BPTREE_OPLIST(FuriEventLoopTree, M_POD_OPLIST)
#define FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX (2)
typedef enum {
FuriEventLoopFlagEvent = (1 << 0),
FuriEventLoopFlagStop = (1 << 1),
} FuriEventLoopFlag;
#define FuriEventLoopFlagAll (FuriEventLoopFlagEvent | FuriEventLoopFlagStop)
typedef enum {
FuriEventLoopProcessStatusComplete,
FuriEventLoopProcessStatusIncomplete,
FuriEventLoopProcessStatusAgain,
} FuriEventLoopProcessStatus;
typedef enum {
FuriEventLoopStateIdle,
FuriEventLoopStateProcessing,
} FuriEventLoopState;
struct FuriEventLoop {
// Only works if all operations are done from the same thread
FuriThreadId thread_id;
// Poller state
volatile FuriEventLoopState state;
// Tree
FuriEventLoopTree_t tree;
// Tree waiting list
WaitingList_t waiting_list;
// Tick event
uint32_t tick_interval;
FuriEventLoopTickCallback tick_callback;
void* tick_callback_context;
};
/*
* Main public API
*/
FuriEventLoop* furi_event_loop_alloc(void) {
FuriEventLoop* instance = malloc(sizeof(FuriEventLoop));
@ -105,6 +48,9 @@ FuriEventLoop* furi_event_loop_alloc(void) {
FuriEventLoopTree_init(instance->tree);
WaitingList_init(instance->waiting_list);
TimerList_init(instance->timer_list);
TimerQueue_init(instance->timer_queue);
PendingQueue_init(instance->pending_queue);
// Clear notification state and value
xTaskNotifyStateClearIndexed(instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX);
@ -117,14 +63,19 @@ FuriEventLoop* furi_event_loop_alloc(void) {
void furi_event_loop_free(FuriEventLoop* instance) {
furi_check(instance);
furi_check(instance->thread_id == furi_thread_get_current_id());
furi_check(instance->state == FuriEventLoopStateStopped);
furi_event_loop_process_timer_queue(instance);
furi_check(TimerList_empty_p(instance->timer_list));
FuriEventLoopTree_clear(instance->tree);
PendingQueue_clear(instance->pending_queue);
uint32_t flags = 0;
BaseType_t ret = xTaskNotifyWaitIndexed(
FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0, FuriEventLoopFlagAll, &flags, 0);
if(ret == pdTRUE) {
FURI_LOG_D(TAG, "Some events was not processed: 0x%lx", flags);
FURI_LOG_D(TAG, "Some events were not processed: 0x%lx", flags);
}
free(instance);
@ -145,33 +96,51 @@ static FuriEventLoopProcessStatus
}
}
static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flags) {
if(flags) {
xTaskNotifyIndexed(
instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, flags, eSetBits);
}
}
void furi_event_loop_run(FuriEventLoop* instance) {
furi_check(instance);
furi_check(instance->thread_id == furi_thread_get_current_id());
furi_event_loop_init_tick(instance);
furi_thread_set_signal_callback(
instance->thread_id, furi_event_loop_signal_callback, instance);
uint32_t timeout = instance->tick_callback ? instance->tick_interval : FuriWaitForever;
while(true) {
instance->state = FuriEventLoopStateIdle;
const TickType_t ticks_to_sleep =
MIN(furi_event_loop_get_timer_wait_time(instance),
furi_event_loop_get_tick_wait_time(instance));
uint32_t flags = 0;
BaseType_t ret = xTaskNotifyWaitIndexed(
FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0, FuriEventLoopFlagAll, &flags, timeout);
FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0, FuriEventLoopFlagAll, &flags, ticks_to_sleep);
instance->state = FuriEventLoopStateProcessing;
if(ret == pdTRUE) {
if(flags & FuriEventLoopFlagStop) {
instance->state = FuriEventLoopStateIdle;
instance->state = FuriEventLoopStateStopped;
break;
} else if(flags & FuriEventLoopFlagEvent) {
FuriEventLoopItem* item = NULL;
FURI_CRITICAL_ENTER();
if(!WaitingList_empty_p(instance->waiting_list)) {
item = WaitingList_pop_front(instance->waiting_list);
WaitingList_init_field(item);
}
FURI_CRITICAL_EXIT();
if(item) {
while(true) {
FuriEventLoopProcessStatus ret =
@ -189,13 +158,23 @@ void furi_event_loop_run(FuriEventLoop* instance) {
}
}
}
furi_event_loop_restore_flags(instance, flags & ~FuriEventLoopFlagEvent);
} else if(flags & FuriEventLoopFlagTimer) {
furi_event_loop_process_timer_queue(instance);
furi_event_loop_restore_flags(instance, flags & ~FuriEventLoopFlagTimer);
} else if(flags & FuriEventLoopFlagPending) {
furi_event_loop_process_pending_callbacks(instance);
} else {
furi_crash();
}
} else {
if(instance->tick_callback) {
instance->tick_callback(instance->tick_callback_context);
}
} else if(!furi_event_loop_process_expired_timers(instance)) {
furi_event_loop_process_tick(instance);
}
instance->state = FuriEventLoopStateIdle;
}
furi_thread_set_signal_callback(instance->thread_id, NULL, NULL);
@ -208,20 +187,33 @@ void furi_event_loop_stop(FuriEventLoop* instance) {
instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagStop, eSetBits);
}
void furi_event_loop_tick_set(
/*
* Public deferred function call API
*/
void furi_event_loop_pend_callback(
FuriEventLoop* instance,
uint32_t interval,
FuriEventLoopTickCallback callback,
FuriEventLoopPendingCallback callback,
void* context) {
furi_check(instance);
furi_check(instance->thread_id == furi_thread_get_current_id());
furi_check(callback ? interval > 0 : true);
furi_check(callback);
instance->tick_interval = interval;
instance->tick_callback = callback;
instance->tick_callback_context = context;
const FuriEventLoopPendingQueueItem item = {
.callback = callback,
.context = context,
};
PendingQueue_push_front(instance->pending_queue, item);
xTaskNotifyIndexed(
instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagPending, eSetBits);
}
/*
* Message queue API
*/
void furi_event_loop_message_queue_subscribe(
FuriEventLoop* instance,
FuriMessageQueue* message_queue,
@ -230,7 +222,7 @@ void furi_event_loop_message_queue_subscribe(
void* context) {
furi_check(instance);
furi_check(instance->thread_id == furi_thread_get_current_id());
furi_check(instance->state == FuriEventLoopStateIdle);
furi_check(instance->state == FuriEventLoopStateStopped);
furi_check(message_queue);
FURI_CRITICAL_ENTER();
@ -267,7 +259,7 @@ void furi_event_loop_message_queue_unsubscribe(
FuriEventLoop* instance,
FuriMessageQueue* message_queue) {
furi_check(instance);
furi_check(instance->state == FuriEventLoopStateIdle);
furi_check(instance->state == FuriEventLoopStateStopped);
furi_check(instance->thread_id == furi_thread_get_current_id());
FURI_CRITICAL_ENTER();

View file

@ -34,7 +34,7 @@ typedef struct FuriEventLoop FuriEventLoop;
* Couple things to keep in mind:
* - You can have 1 event_loop per 1 thread
* - You can not use event_loop instance in the other thread
* - Do not use blocking api to query object delegated to Event Loop
* - Do not use blocking API to query object delegated to Event Loop
*
* @return The Event Loop instance
*/
@ -72,8 +72,10 @@ typedef void (*FuriEventLoopTickCallback)(void* context);
/** Set Event Loop tick callback
*
* Tick callback called after specified inactivity time. It's not periodic. If
* Event Loop is busy then ticks will be skipped.
* Tick callback is called periodically after specified inactivity time.
* It acts like a low-priority timer: it will only fire if there is time
* left after processing the synchronization primitives and the regular timers.
* Therefore, it is not monotonic: ticks will be skipped if the event loop is busy.
*
* @param instance The Event Loop instance
* @param[in] interval The tick interval
@ -86,6 +88,32 @@ void furi_event_loop_tick_set(
FuriEventLoopTickCallback callback,
void* context);
/*
* Deferred function call API
*/
/**
* @brief Timer callback type for functions to be called in a deferred manner.
*
* @param[in,out] context pointer to a user-specific object that was provided during
* furi_event_loop_pend_callback() call
*/
typedef void (*FuriEventLoopPendingCallback)(void* context);
/**
* @brief Call a function when all preceding timer commands are processed
*
* This function may be useful to call another function when the event loop has been started.
*
* @param[in,out] instance pointer to the current FuriEventLoop instance
* @param[in] callback pointer to the callback to be executed when previous commands have been processed
* @param[in,out] context pointer to a user-specific object (will be passed to the callback)
*/
void furi_event_loop_pend_callback(
FuriEventLoop* instance,
FuriEventLoopPendingCallback callback,
void* context);
/*
* Message queue related APIs
*/

View file

@ -1,35 +1,97 @@
#pragma once
#include "event_loop.h"
#include "event_loop_link_i.h"
#include "event_loop_timer_i.h"
#include "event_loop_tick_i.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <m-list.h>
#include <m-bptree.h>
#include <m-i-list.h>
typedef struct FuriEventLoopItem FuriEventLoopItem;
#include "thread.h"
/* Link between Event Loop */
struct FuriEventLoopItem {
// Source
FuriEventLoop* owner;
// Tracking item
const FuriEventLoopContract* contract;
void* object;
FuriEventLoopEvent event;
// Callback and context
FuriEventLoopMessageQueueCallback callback;
void* callback_context;
// Waiting list
ILIST_INTERFACE(WaitingList, struct FuriEventLoopItem);
};
ILIST_DEF(WaitingList, FuriEventLoopItem, M_POD_OPLIST)
/* Event Loop RB tree */
#define FURI_EVENT_LOOP_TREE_RANK (4)
BPTREE_DEF2( // NOLINT
FuriEventLoopTree,
FURI_EVENT_LOOP_TREE_RANK,
void*, /* pointer to object we track */
M_PTR_OPLIST,
FuriEventLoopItem*, /* pointer to the FuriEventLoopItem */
M_PTR_OPLIST)
#define M_OPL_FuriEventLoopTree_t() BPTREE_OPLIST(FuriEventLoopTree, M_POD_OPLIST)
#define FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX (2)
typedef enum {
FuriEventLoopFlagEvent = (1 << 0),
FuriEventLoopFlagStop = (1 << 1),
FuriEventLoopFlagTimer = (1 << 2),
FuriEventLoopFlagPending = (1 << 3),
} FuriEventLoopFlag;
#define FuriEventLoopFlagAll \
(FuriEventLoopFlagEvent | FuriEventLoopFlagStop | FuriEventLoopFlagTimer | \
FuriEventLoopFlagPending)
typedef enum {
FuriEventLoopProcessStatusComplete,
FuriEventLoopProcessStatusIncomplete,
FuriEventLoopProcessStatusAgain,
} FuriEventLoopProcessStatus;
typedef enum {
FuriEventLoopStateStopped,
FuriEventLoopStateIdle,
FuriEventLoopStateProcessing,
} FuriEventLoopState;
typedef struct {
FuriEventLoopItem* item_in;
FuriEventLoopItem* item_out;
} FuriEventLoopLink;
FuriEventLoopPendingCallback callback;
void* context;
} FuriEventLoopPendingQueueItem;
void furi_event_loop_link_notify(FuriEventLoopLink* instance, FuriEventLoopEvent event);
LIST_DUAL_PUSH_DEF(PendingQueue, FuriEventLoopPendingQueueItem, M_POD_OPLIST)
/* Contract between event loop and an object */
struct FuriEventLoop {
// Only works if all operations are done from the same thread
FuriThreadId thread_id;
typedef FuriEventLoopLink* (*FuriEventLoopContractGetLink)(void* object);
// Poller state
volatile FuriEventLoopState state;
typedef uint32_t (*FuriEventLoopContractGetLevel)(void* object, FuriEventLoopEvent event);
// Event handling
FuriEventLoopTree_t tree;
WaitingList_t waiting_list;
typedef struct {
const FuriEventLoopContractGetLink get_link;
const FuriEventLoopContractGetLevel get_level;
} FuriEventLoopContract;
bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context);
#ifdef __cplusplus
}
#endif
// Active timer list
TimerList_t timer_list;
// Timer request queue
TimerQueue_t timer_queue;
// Pending callback queue
PendingQueue_t pending_queue;
// Tick event
FuriEventLoopTick tick;
};

View file

@ -0,0 +1,35 @@
#pragma once
#include "event_loop.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct FuriEventLoopItem FuriEventLoopItem;
/* Link between Event Loop */
typedef struct {
FuriEventLoopItem* item_in;
FuriEventLoopItem* item_out;
} FuriEventLoopLink;
void furi_event_loop_link_notify(FuriEventLoopLink* instance, FuriEventLoopEvent event);
/* Contract between event loop and an object */
typedef FuriEventLoopLink* (*FuriEventLoopContractGetLink)(void* object);
typedef uint32_t (*FuriEventLoopContractGetLevel)(void* object, FuriEventLoopEvent event);
typedef struct {
const FuriEventLoopContractGetLink get_link;
const FuriEventLoopContractGetLevel get_level;
} FuriEventLoopContract;
bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,69 @@
#include "event_loop_i.h"
#include <FreeRTOS.h>
#include <task.h>
#include <furi.h>
/**
* Private functions
*/
static inline uint32_t furi_event_loop_tick_get_elapsed_time(const FuriEventLoop* instance) {
return xTaskGetTickCount() - instance->tick.prev_time;
}
static inline uint32_t furi_event_loop_tick_get_remaining_time(const FuriEventLoop* instance) {
const uint32_t elapsed_time = furi_event_loop_tick_get_elapsed_time(instance);
return elapsed_time < instance->tick.interval ? instance->tick.interval - elapsed_time : 0;
}
static inline bool furi_event_loop_tick_is_expired(const FuriEventLoop* instance) {
return furi_event_loop_tick_get_elapsed_time(instance) >= instance->tick.interval;
}
/*
* Private tick API
*/
void furi_event_loop_init_tick(FuriEventLoop* instance) {
if(instance->tick.callback) {
instance->tick.prev_time = xTaskGetTickCount();
}
}
void furi_event_loop_process_tick(FuriEventLoop* instance) {
if(instance->tick.callback && furi_event_loop_tick_is_expired(instance)) {
instance->tick.prev_time += instance->tick.interval;
instance->tick.callback(instance->tick.callback_context);
}
}
uint32_t furi_event_loop_get_tick_wait_time(const FuriEventLoop* instance) {
uint32_t wait_time = FuriWaitForever;
if(instance->tick.callback) {
wait_time = furi_event_loop_tick_get_remaining_time(instance);
}
return wait_time;
}
/*
* Public tick API
*/
void furi_event_loop_tick_set(
FuriEventLoop* instance,
uint32_t interval,
FuriEventLoopTickCallback callback,
void* context) {
furi_check(instance);
furi_check(instance->thread_id == furi_thread_get_current_id());
furi_check(callback ? interval > 0 : true);
instance->tick.callback = callback;
instance->tick.callback_context = context;
instance->tick.interval = interval;
instance->tick.prev_time = xTaskGetTickCount();
}

View file

@ -0,0 +1,16 @@
#pragma once
#include "event_loop.h"
typedef struct {
uint32_t interval;
uint32_t prev_time;
FuriEventLoopTickCallback callback;
void* callback_context;
} FuriEventLoopTick;
void furi_event_loop_init_tick(FuriEventLoop* instance);
void furi_event_loop_process_tick(FuriEventLoop* instance);
uint32_t furi_event_loop_get_tick_wait_time(const FuriEventLoop* instance);

View file

@ -0,0 +1,215 @@
#include "event_loop_i.h"
#include <FreeRTOS.h>
#include <task.h>
#include <furi.h>
/*
* Private functions
*/
static inline uint32_t furi_event_loop_timer_get_elapsed_time(const FuriEventLoopTimer* timer) {
return xTaskGetTickCount() - timer->start_time;
}
static inline uint32_t
furi_event_loop_timer_get_remaining_time_private(const FuriEventLoopTimer* timer) {
const uint32_t elapsed_time = furi_event_loop_timer_get_elapsed_time(timer);
return elapsed_time < timer->interval ? timer->interval - elapsed_time : 0;
}
static inline bool furi_event_loop_timer_is_expired(const FuriEventLoopTimer* timer) {
return furi_event_loop_timer_get_elapsed_time(timer) >= timer->interval;
}
static void furi_event_loop_schedule_timer(FuriEventLoop* instance, FuriEventLoopTimer* timer) {
FuriEventLoopTimer* timer_pos = NULL;
FURI_CRITICAL_ENTER();
const uint32_t remaining_time = furi_event_loop_timer_get_remaining_time_private(timer);
TimerList_it_t it;
for(TimerList_it_last(it, instance->timer_list); !TimerList_end_p(it);
TimerList_previous(it)) {
FuriEventLoopTimer* tmp = TimerList_ref(it);
if(remaining_time >= furi_event_loop_timer_get_remaining_time_private(tmp)) {
timer_pos = tmp;
break;
}
}
FURI_CRITICAL_EXIT();
if(timer_pos) {
TimerList_push_after(timer_pos, timer);
} else {
TimerList_push_front(instance->timer_list, timer);
}
// At this point, TimerList_front() points to the first timer to expire
}
static void furi_event_loop_timer_enqueue_request(
FuriEventLoopTimer* timer,
FuriEventLoopTimerRequest request) {
if(timer->request != FuriEventLoopTimerRequestNone) {
// You cannot change your mind after calling furi_event_loop_timer_free()
furi_check(timer->request != FuriEventLoopTimerRequestFree);
TimerQueue_unlink(timer);
}
timer->request = request;
FuriEventLoop* instance = timer->owner;
TimerQueue_push_back(instance->timer_queue, timer);
xTaskNotifyIndexed(
instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagTimer, eSetBits);
}
/*
* Private API
*/
uint32_t furi_event_loop_get_timer_wait_time(const FuriEventLoop* instance) {
uint32_t wait_time = FuriWaitForever;
if(!TimerList_empty_p(instance->timer_list)) {
FuriEventLoopTimer* timer = TimerList_front(instance->timer_list);
wait_time = furi_event_loop_timer_get_remaining_time_private(timer);
}
return wait_time;
}
void furi_event_loop_process_timer_queue(FuriEventLoop* instance) {
while(!TimerQueue_empty_p(instance->timer_queue)) {
FuriEventLoopTimer* timer = TimerQueue_pop_front(instance->timer_queue);
if(timer->active) {
TimerList_unlink(timer);
}
if(timer->request == FuriEventLoopTimerRequestStart) {
timer->active = true;
timer->interval = timer->next_interval;
timer->start_time = xTaskGetTickCount();
timer->request = FuriEventLoopTimerRequestNone;
furi_event_loop_schedule_timer(instance, timer);
} else if(timer->request == FuriEventLoopTimerRequestStop) {
timer->active = false;
timer->request = FuriEventLoopTimerRequestNone;
} else if(timer->request == FuriEventLoopTimerRequestFree) {
free(timer);
} else {
furi_crash();
}
}
}
bool furi_event_loop_process_expired_timers(FuriEventLoop* instance) {
if(TimerList_empty_p(instance->timer_list)) {
return false;
}
// The front() element contains the earliest-expiring timer
FuriEventLoopTimer* timer = TimerList_front(instance->timer_list);
if(!furi_event_loop_timer_is_expired(timer)) {
return false;
}
TimerList_unlink(timer);
if(timer->periodic) {
const uint32_t num_events =
furi_event_loop_timer_get_elapsed_time(timer) / timer->interval;
timer->start_time += timer->interval * num_events;
furi_event_loop_schedule_timer(instance, timer);
} else {
timer->active = false;
}
timer->callback(timer->context);
return true;
}
/*
* Public timer API
*/
FuriEventLoopTimer* furi_event_loop_timer_alloc(
FuriEventLoop* instance,
FuriEventLoopTimerCallback callback,
FuriEventLoopTimerType type,
void* context) {
furi_check(instance);
furi_check(instance->thread_id == furi_thread_get_current_id());
furi_check(callback);
furi_check(type <= FuriEventLoopTimerTypePeriodic);
FuriEventLoopTimer* timer = malloc(sizeof(FuriEventLoopTimer));
timer->owner = instance;
timer->callback = callback;
timer->context = context;
timer->periodic = (type == FuriEventLoopTimerTypePeriodic);
TimerList_init_field(timer);
TimerQueue_init_field(timer);
return timer;
}
void furi_event_loop_timer_free(FuriEventLoopTimer* timer) {
furi_check(timer);
furi_check(timer->owner->thread_id == furi_thread_get_current_id());
furi_event_loop_timer_enqueue_request(timer, FuriEventLoopTimerRequestFree);
}
void furi_event_loop_timer_start(FuriEventLoopTimer* timer, uint32_t interval) {
furi_check(timer);
furi_check(timer->owner->thread_id == furi_thread_get_current_id());
timer->next_interval = interval;
furi_event_loop_timer_enqueue_request(timer, FuriEventLoopTimerRequestStart);
}
void furi_event_loop_timer_restart(FuriEventLoopTimer* timer) {
furi_check(timer);
furi_check(timer->owner->thread_id == furi_thread_get_current_id());
timer->next_interval = timer->interval;
furi_event_loop_timer_enqueue_request(timer, FuriEventLoopTimerRequestStart);
}
void furi_event_loop_timer_stop(FuriEventLoopTimer* timer) {
furi_check(timer);
furi_check(timer->owner->thread_id == furi_thread_get_current_id());
furi_event_loop_timer_enqueue_request(timer, FuriEventLoopTimerRequestStop);
}
uint32_t furi_event_loop_timer_get_remaining_time(const FuriEventLoopTimer* timer) {
furi_check(timer);
return furi_event_loop_timer_get_remaining_time_private(timer);
}
uint32_t furi_event_loop_timer_get_interval(const FuriEventLoopTimer* timer) {
furi_check(timer);
return timer->interval;
}
bool furi_event_loop_timer_is_running(const FuriEventLoopTimer* timer) {
furi_check(timer);
return timer->active;
}

View file

@ -0,0 +1,118 @@
/**
* @file event_loop_timer.h
* @brief Software timer functionality for FuriEventLoop.
*/
#pragma once
#include "event_loop.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Enumeration of possible timer types.
*/
typedef enum {
FuriEventLoopTimerTypeOnce = 0, /**< One-shot timer. */
FuriEventLoopTimerTypePeriodic = 1, /**< Repeating timer. */
} FuriEventLoopTimerType;
/**
* @brief Timer callback type for functions to be called when a timer expires.
*
* In the timer callback, it is ALLOWED:
* - To start, stop, or restart an existing timer,
* - To create new timers using furi_event_loop_timer_alloc(),
* - To delete timers using furi_event_loop_timer_free().
*
* @param[in,out] context pointer to a user-specific object that was provided during timer creation
*/
typedef void (*FuriEventLoopTimerCallback)(void* context);
/**
* @brief Opaque event loop timer type.
*/
typedef struct FuriEventLoopTimer FuriEventLoopTimer;
/**
* @brief Create a new event loop timer instance.
*
* @param[in,out] instance pointer to the current FuriEventLoop instance
* @param[in] callback pointer to the callback function to be executed upon timer timeout
* @param[in] type timer type value to determine its behavior (single-shot or periodic)
* @param[in,out] context pointer to a user-specific object (will be passed to the callback)
* @returns pointer to the created timer instance
*/
FuriEventLoopTimer* furi_event_loop_timer_alloc(
FuriEventLoop* instance,
FuriEventLoopTimerCallback callback,
FuriEventLoopTimerType type,
void* context);
/**
* @brief Delete an event loop timer instance.
*
* @warning The user code MUST call furi_event_loop_timer_free() on ALL instances
* associated with the current event loop BEFORE calling furi_event_loop_free().
* The event loop may EITHER be running OR stopped when the timers are being deleted.
*
* @param[in,out] timer pointer to the timer instance to be deleted
*/
void furi_event_loop_timer_free(FuriEventLoopTimer* timer);
/**
* @brief Start a timer or restart it with a new interval.
*
* @param[in,out] timer pointer to the timer instance to be (re)started
* @param[in] interval timer interval in ticks
*/
void furi_event_loop_timer_start(FuriEventLoopTimer* timer, uint32_t interval);
/**
* @brief Restart a timer with the previously set interval.
*
* @param[in,out] timer pointer to the timer instance to be restarted
*/
void furi_event_loop_timer_restart(FuriEventLoopTimer* timer);
/**
* @brief Stop a timer without firing its callback.
*
* It is safe to call this function on an already stopped timer (it will do nothing).
*
* @param[in,out] timer pointer to the timer instance to be stopped
*/
void furi_event_loop_timer_stop(FuriEventLoopTimer* timer);
/**
* @brief Get the time remaining before the timer becomes expires.
*
* For stopped or expired timers, this function returns 0.
*
* @param[in] timer pointer to the timer to be queried
* @returns remaining time in ticks
*/
uint32_t furi_event_loop_timer_get_remaining_time(const FuriEventLoopTimer* timer);
/**
* @brief Get the timer interval.
*
* @param[in] timer pointer to the timer to be queried
* @returns timer interval in ticks
*/
uint32_t furi_event_loop_timer_get_interval(const FuriEventLoopTimer* timer);
/**
* @brief Check if the timer is currently running.
*
* A timer is considered running if it has not expired yet.
* @param[in] timer pointer to the timer to be queried
* @returns true if the timer is running, false otherwise
*/
bool furi_event_loop_timer_is_running(const FuriEventLoopTimer* timer);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,43 @@
#pragma once
#include "event_loop_timer.h"
#include <m-i-list.h>
typedef enum {
FuriEventLoopTimerRequestNone,
FuriEventLoopTimerRequestStart,
FuriEventLoopTimerRequestStop,
FuriEventLoopTimerRequestFree,
} FuriEventLoopTimerRequest;
struct FuriEventLoopTimer {
FuriEventLoop* owner;
FuriEventLoopTimerCallback callback;
void* context;
uint32_t interval;
uint32_t start_time;
uint32_t next_interval;
// Interface for the active timer list
ILIST_INTERFACE(TimerList, FuriEventLoopTimer);
// Interface for the timer request queue
ILIST_INTERFACE(TimerQueue, FuriEventLoopTimer);
FuriEventLoopTimerRequest request;
bool active;
bool periodic;
};
ILIST_DEF(TimerList, FuriEventLoopTimer, M_POD_OPLIST)
ILIST_DEF(TimerQueue, FuriEventLoopTimer, M_POD_OPLIST)
uint32_t furi_event_loop_get_timer_wait_time(const FuriEventLoop* instance);
void furi_event_loop_process_timer_queue(FuriEventLoop* instance);
bool furi_event_loop_process_expired_timers(FuriEventLoop* instance);

View file

@ -1,5 +1,11 @@
#include "message_queue_i.h"
#include <FreeRTOS.h>
#include <queue.h>
#include "kernel.h"
#include "check.h"
// Internal FreeRTOS member names
#define uxMessagesWaiting uxDummy4[0]
#define uxLength uxDummy4[1]

View file

@ -1,12 +1,6 @@
#pragma once
#include "message_queue.h"
#include "kernel.h"
#include "event_loop_i.h"
#include "check.h"
#include <FreeRTOS.h>
#include <queue.h>
#include "event_loop_link_i.h"
extern const FuriEventLoopContract furi_message_queue_event_loop_contract;

View file

@ -5,6 +5,7 @@
#include "core/check.h"
#include "core/common_defines.h"
#include "core/event_loop.h"
#include "core/event_loop_timer.h"
#include "core/event_flag.h"
#include "core/kernel.h"
#include "core/log.h"

View file

@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,67.1,,
Version,+,67.2,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
Header,+,applications/services/cli/cli.h,,
@ -1105,13 +1105,22 @@ Function,+,furi_event_flag_free,void,FuriEventFlag*
Function,+,furi_event_flag_get,uint32_t,FuriEventFlag*
Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t"
Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t"
Function,-,furi_event_loop_alloc,FuriEventLoop*,
Function,-,furi_event_loop_free,void,FuriEventLoop*
Function,-,furi_event_loop_message_queue_subscribe,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopMessageQueueCallback, void*"
Function,-,furi_event_loop_message_queue_unsubscribe,void,"FuriEventLoop*, FuriMessageQueue*"
Function,-,furi_event_loop_run,void,FuriEventLoop*
Function,-,furi_event_loop_stop,void,FuriEventLoop*
Function,-,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*"
Function,+,furi_event_loop_alloc,FuriEventLoop*,
Function,+,furi_event_loop_free,void,FuriEventLoop*
Function,+,furi_event_loop_message_queue_subscribe,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopMessageQueueCallback, void*"
Function,+,furi_event_loop_message_queue_unsubscribe,void,"FuriEventLoop*, FuriMessageQueue*"
Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*"
Function,+,furi_event_loop_run,void,FuriEventLoop*
Function,+,furi_event_loop_stop,void,FuriEventLoop*
Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*"
Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*"
Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer*
Function,+,furi_event_loop_timer_get_interval,uint32_t,const FuriEventLoopTimer*
Function,+,furi_event_loop_timer_get_remaining_time,uint32_t,const FuriEventLoopTimer*
Function,+,furi_event_loop_timer_is_running,_Bool,const FuriEventLoopTimer*
Function,+,furi_event_loop_timer_restart,void,FuriEventLoopTimer*
Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t"
Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer*
Function,+,furi_get_tick,uint32_t,
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*

1 entry status name type params
2 Version + 67.1 67.2
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/bt/bt_service/bt_keys_storage.h
5 Header + applications/services/cli/cli.h
1105 Function + furi_event_flag_get uint32_t FuriEventFlag*
1106 Function + furi_event_flag_set uint32_t FuriEventFlag*, uint32_t
1107 Function + furi_event_flag_wait uint32_t FuriEventFlag*, uint32_t, uint32_t, uint32_t
1108 Function - + furi_event_loop_alloc FuriEventLoop*
1109 Function - + furi_event_loop_free void FuriEventLoop*
1110 Function - + furi_event_loop_message_queue_subscribe void FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopMessageQueueCallback, void*
1111 Function - + furi_event_loop_message_queue_unsubscribe void FuriEventLoop*, FuriMessageQueue*
1112 Function - + furi_event_loop_run furi_event_loop_pend_callback void FuriEventLoop* FuriEventLoop*, FuriEventLoopPendingCallback, void*
1113 Function - + furi_event_loop_stop furi_event_loop_run void FuriEventLoop*
1114 Function - + furi_event_loop_tick_set furi_event_loop_stop void FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void* FuriEventLoop*
1115 Function + furi_event_loop_tick_set void FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*
1116 Function + furi_event_loop_timer_alloc FuriEventLoopTimer* FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*
1117 Function + furi_event_loop_timer_free void FuriEventLoopTimer*
1118 Function + furi_event_loop_timer_get_interval uint32_t const FuriEventLoopTimer*
1119 Function + furi_event_loop_timer_get_remaining_time uint32_t const FuriEventLoopTimer*
1120 Function + furi_event_loop_timer_is_running _Bool const FuriEventLoopTimer*
1121 Function + furi_event_loop_timer_restart void FuriEventLoopTimer*
1122 Function + furi_event_loop_timer_start void FuriEventLoopTimer*, uint32_t
1123 Function + furi_event_loop_timer_stop void FuriEventLoopTimer*
1124 Function + furi_get_tick uint32_t
1125 Function + furi_hal_adc_acquire FuriHalAdcHandle*
1126 Function + furi_hal_adc_configure void FuriHalAdcHandle*

View file

@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,67.1,,
Version,+,67.2,,
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
@ -1210,13 +1210,22 @@ Function,+,furi_event_flag_free,void,FuriEventFlag*
Function,+,furi_event_flag_get,uint32_t,FuriEventFlag*
Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t"
Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t"
Function,-,furi_event_loop_alloc,FuriEventLoop*,
Function,-,furi_event_loop_free,void,FuriEventLoop*
Function,-,furi_event_loop_message_queue_subscribe,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopMessageQueueCallback, void*"
Function,-,furi_event_loop_message_queue_unsubscribe,void,"FuriEventLoop*, FuriMessageQueue*"
Function,-,furi_event_loop_run,void,FuriEventLoop*
Function,-,furi_event_loop_stop,void,FuriEventLoop*
Function,-,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*"
Function,+,furi_event_loop_alloc,FuriEventLoop*,
Function,+,furi_event_loop_free,void,FuriEventLoop*
Function,+,furi_event_loop_message_queue_subscribe,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopMessageQueueCallback, void*"
Function,+,furi_event_loop_message_queue_unsubscribe,void,"FuriEventLoop*, FuriMessageQueue*"
Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*"
Function,+,furi_event_loop_run,void,FuriEventLoop*
Function,+,furi_event_loop_stop,void,FuriEventLoop*
Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*"
Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*"
Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer*
Function,+,furi_event_loop_timer_get_interval,uint32_t,const FuriEventLoopTimer*
Function,+,furi_event_loop_timer_get_remaining_time,uint32_t,const FuriEventLoopTimer*
Function,+,furi_event_loop_timer_is_running,_Bool,const FuriEventLoopTimer*
Function,+,furi_event_loop_timer_restart,void,FuriEventLoopTimer*
Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t"
Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer*
Function,+,furi_get_tick,uint32_t,
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*

1 entry status name type params
2 Version + 67.1 67.2
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/bt/bt_service/bt_keys_storage.h
1210 Function + furi_event_flag_get uint32_t FuriEventFlag*
1211 Function + furi_event_flag_set uint32_t FuriEventFlag*, uint32_t
1212 Function + furi_event_flag_wait uint32_t FuriEventFlag*, uint32_t, uint32_t, uint32_t
1213 Function - + furi_event_loop_alloc FuriEventLoop*
1214 Function - + furi_event_loop_free void FuriEventLoop*
1215 Function - + furi_event_loop_message_queue_subscribe void FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopMessageQueueCallback, void*
1216 Function - + furi_event_loop_message_queue_unsubscribe void FuriEventLoop*, FuriMessageQueue*
1217 Function - + furi_event_loop_run furi_event_loop_pend_callback void FuriEventLoop* FuriEventLoop*, FuriEventLoopPendingCallback, void*
1218 Function - + furi_event_loop_stop furi_event_loop_run void FuriEventLoop*
1219 Function - + furi_event_loop_tick_set furi_event_loop_stop void FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void* FuriEventLoop*
1220 Function + furi_event_loop_tick_set void FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*
1221 Function + furi_event_loop_timer_alloc FuriEventLoopTimer* FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*
1222 Function + furi_event_loop_timer_free void FuriEventLoopTimer*
1223 Function + furi_event_loop_timer_get_interval uint32_t const FuriEventLoopTimer*
1224 Function + furi_event_loop_timer_get_remaining_time uint32_t const FuriEventLoopTimer*
1225 Function + furi_event_loop_timer_is_running _Bool const FuriEventLoopTimer*
1226 Function + furi_event_loop_timer_restart void FuriEventLoopTimer*
1227 Function + furi_event_loop_timer_start void FuriEventLoopTimer*, uint32_t
1228 Function + furi_event_loop_timer_stop void FuriEventLoopTimer*
1229 Function + furi_get_tick uint32_t
1230 Function + furi_hal_adc_acquire FuriHalAdcHandle*
1231 Function + furi_hal_adc_configure void FuriHalAdcHandle*