mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-10 06:54:19 +00:00
[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:
parent
bf90843f25
commit
139660d206
20 changed files with 1087 additions and 256 deletions
|
@ -3,10 +3,12 @@
|
||||||
//-V:M_EACH:1048,1044
|
//-V:M_EACH:1048,1044
|
||||||
//-V:ARRAY_DEF:760,747,568,776,729,712,654,1103
|
//-V:ARRAY_DEF:760,747,568,776,729,712,654,1103
|
||||||
//-V:LIST_DEF:760,747,568,712,729,654,776,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:BPTREE_DEF2:779,1086,557,773,512
|
||||||
//-V:DICT_DEF2:779,524,776,760,1044,1001,729,590,568,747,685,1103
|
//-V:DICT_DEF2:779,524,776,760,1044,1001,729,590,568,747,685,1103
|
||||||
//-V:ALGO_DEF:1048,747,1044
|
//-V:ALGO_DEF:1048,747,1044
|
||||||
//-V:TUPLE_DEF2:524,590,1001,760
|
//-V:TUPLE_DEF2:524,590,1001,760
|
||||||
|
//-V:DEQUE_DEF:658,747,760
|
||||||
|
|
||||||
# Non-severe malloc/null pointer deref warnings
|
# Non-severe malloc/null pointer deref warnings
|
||||||
//-V::522:2,3
|
//-V::522:2,3
|
||||||
|
|
10
applications/debug/event_loop_blink_test/application.fam
Normal file
10
applications/debug/event_loop_blink_test/application.fam
Normal 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",
|
||||||
|
)
|
169
applications/debug/event_loop_blink_test/event_loop_blink_test.c
Normal file
169
applications/debug/event_loop_blink_test/event_loop_blink_test.c
Normal 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;
|
||||||
|
}
|
|
@ -1,22 +1,48 @@
|
||||||
#include "dolphin.h"
|
|
||||||
#include "helpers/dolphin_state.h"
|
|
||||||
#include "dolphin_i.h"
|
#include "dolphin_i.h"
|
||||||
|
|
||||||
#include <furi_hal.h>
|
#include <furi_hal.h>
|
||||||
#include <stdint.h>
|
|
||||||
#include <furi.h>
|
|
||||||
#define DOLPHIN_LOCK_EVENT_FLAG (0x1)
|
|
||||||
|
|
||||||
#define TAG "Dolphin"
|
#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) {
|
void dolphin_deed(DolphinDeed deed) {
|
||||||
Dolphin* dolphin = (Dolphin*)furi_record_open(RECORD_DOLPHIN);
|
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||||
|
|
||||||
DolphinEvent event;
|
DolphinEvent event;
|
||||||
event.type = DolphinEventTypeDeed;
|
event.type = DolphinEventTypeDeed;
|
||||||
event.deed = deed;
|
event.deed = deed;
|
||||||
|
|
||||||
dolphin_event_send_async(dolphin, &event);
|
dolphin_event_send_async(dolphin, &event);
|
||||||
|
|
||||||
furi_record_close(RECORD_DOLPHIN);
|
furi_record_close(RECORD_DOLPHIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,52 +69,75 @@ void dolphin_flush(Dolphin* dolphin) {
|
||||||
dolphin_event_send_wait(dolphin, &event);
|
dolphin_event_send_wait(dolphin, &event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dolphin_butthurt_timer_callback(void* context) {
|
void dolphin_upgrade_level(Dolphin* dolphin) {
|
||||||
Dolphin* dolphin = context;
|
furi_check(dolphin);
|
||||||
furi_assert(dolphin);
|
|
||||||
|
|
||||||
DolphinEvent event;
|
DolphinEvent event;
|
||||||
event.type = DolphinEventTypeIncreaseButthurt;
|
event.type = DolphinEventTypeLevel;
|
||||||
|
|
||||||
dolphin_event_send_async(dolphin, &event);
|
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;
|
Dolphin* dolphin = context;
|
||||||
furi_assert(dolphin);
|
furi_assert(dolphin);
|
||||||
|
|
||||||
DolphinEvent event;
|
FURI_LOG_I(TAG, "Increase butthurt");
|
||||||
event.type = DolphinEventTypeFlush;
|
dolphin_state_butthurted(dolphin->state);
|
||||||
dolphin_event_send_async(dolphin, &event);
|
dolphin_state_save(dolphin->state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dolphin_clear_limits_timer_callback(void* context) {
|
static void dolphin_flush_timer_callback(void* context) {
|
||||||
Dolphin* dolphin = context;
|
Dolphin* dolphin = context;
|
||||||
furi_assert(dolphin);
|
furi_assert(dolphin);
|
||||||
|
|
||||||
furi_timer_start(dolphin->clear_limits_timer, HOURS_IN_TICKS(24));
|
FURI_LOG_I(TAG, "Flush stats");
|
||||||
|
dolphin_state_save(dolphin->state);
|
||||||
DolphinEvent event;
|
|
||||||
event.type = DolphinEventTypeClearLimits;
|
|
||||||
dolphin_event_send_async(dolphin, &event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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* dolphin = malloc(sizeof(Dolphin));
|
||||||
|
|
||||||
dolphin->state = dolphin_state_alloc();
|
dolphin->state = dolphin_state_alloc();
|
||||||
dolphin->event_queue = furi_message_queue_alloc(8, sizeof(DolphinEvent));
|
|
||||||
dolphin->pubsub = furi_pubsub_alloc();
|
dolphin->pubsub = furi_pubsub_alloc();
|
||||||
dolphin->butthurt_timer =
|
dolphin->event_queue = furi_message_queue_alloc(EVENT_QUEUE_SIZE, sizeof(DolphinEvent));
|
||||||
furi_timer_alloc(dolphin_butthurt_timer_callback, FuriTimerTypePeriodic, dolphin);
|
dolphin->event_loop = furi_event_loop_alloc();
|
||||||
dolphin->flush_timer =
|
|
||||||
furi_timer_alloc(dolphin_flush_timer_callback, FuriTimerTypeOnce, dolphin);
|
dolphin->butthurt_timer = furi_event_loop_timer_alloc(
|
||||||
dolphin->clear_limits_timer =
|
dolphin->event_loop,
|
||||||
furi_timer_alloc(dolphin_clear_limits_timer_callback, FuriTimerTypePeriodic, dolphin);
|
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;
|
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(dolphin);
|
||||||
furi_assert(event);
|
furi_assert(event);
|
||||||
event->flag = NULL;
|
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);
|
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(dolphin);
|
||||||
furi_assert(event);
|
furi_assert(event);
|
||||||
|
|
||||||
|
@ -110,39 +159,81 @@ void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event) {
|
||||||
furi_event_flag_free(event->flag);
|
furi_event_flag_free(event->flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dolphin_event_release(Dolphin* dolphin, DolphinEvent* event) {
|
static void dolphin_event_release(DolphinEvent* event) {
|
||||||
UNUSED(dolphin);
|
|
||||||
if(event->flag) {
|
if(event->flag) {
|
||||||
furi_event_flag_set(event->flag, DOLPHIN_LOCK_EVENT_FLAG);
|
furi_event_flag_set(event->flag, DOLPHIN_LOCK_EVENT_FLAG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin) {
|
static void dolphin_update_clear_limits_timer_period(void* context) {
|
||||||
furi_check(dolphin);
|
furi_assert(context);
|
||||||
return dolphin->pubsub;
|
Dolphin* dolphin = context;
|
||||||
}
|
|
||||||
|
|
||||||
static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) {
|
uint32_t time_to_clear_limits =
|
||||||
furi_assert(dolphin);
|
furi_event_loop_timer_get_remaining_time(dolphin->clear_limits_timer);
|
||||||
uint32_t now_ticks = furi_get_tick();
|
|
||||||
uint32_t timer_expires_at = furi_timer_get_expire_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;
|
DateTime date;
|
||||||
furi_hal_rtc_get_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) {
|
const uint32_t now_time_ticks = DATE_IN_TICKS(date.hour, date.minute, date.second);
|
||||||
time_to_clear_limits = HOURS_IN_TICKS(5) - now_time_in_ms;
|
|
||||||
|
if(date.hour < CLEAR_LIMITS_TIME_HOURS) {
|
||||||
|
time_to_clear_limits = CLEAR_LIMITS_TIME_TICKS - now_time_ticks;
|
||||||
} else {
|
} 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) {
|
int32_t dolphin_srv(void* p) {
|
||||||
UNUSED(p);
|
UNUSED(p);
|
||||||
|
|
||||||
|
@ -157,54 +248,27 @@ int32_t dolphin_srv(void* p) {
|
||||||
furi_record_create(RECORD_DOLPHIN, dolphin);
|
furi_record_create(RECORD_DOLPHIN, dolphin);
|
||||||
|
|
||||||
dolphin_state_load(dolphin->state);
|
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;
|
furi_event_loop_message_queue_subscribe(
|
||||||
while(1) {
|
dolphin->event_loop,
|
||||||
if(furi_message_queue_get(dolphin->event_queue, &event, HOURS_IN_TICKS(1)) ==
|
dolphin->event_queue,
|
||||||
FuriStatusOk) {
|
FuriEventLoopEventIn,
|
||||||
if(event.type == DolphinEventTypeDeed) {
|
dolphin_process_event,
|
||||||
dolphin_state_on_deed(dolphin->state, event.deed);
|
dolphin);
|
||||||
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_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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dolphin_upgrade_level(Dolphin* dolphin) {
|
|
||||||
furi_check(dolphin);
|
|
||||||
|
|
||||||
dolphin_state_increase_level(dolphin->state);
|
|
||||||
dolphin_flush(dolphin);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "helpers/dolphin_deed.h"
|
|
||||||
|
|
||||||
#include <gui/view.h>
|
|
||||||
#include <core/pubsub.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <core/pubsub.h>
|
||||||
|
|
||||||
|
#include "helpers/dolphin_deed.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <core/pubsub.h>
|
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <furi_hal.h>
|
|
||||||
|
#include <core/pubsub.h>
|
||||||
|
|
||||||
#include "dolphin.h"
|
#include "dolphin.h"
|
||||||
#include "helpers/dolphin_state.h"
|
#include "helpers/dolphin_state.h"
|
||||||
|
@ -11,8 +11,7 @@ typedef enum {
|
||||||
DolphinEventTypeDeed,
|
DolphinEventTypeDeed,
|
||||||
DolphinEventTypeStats,
|
DolphinEventTypeStats,
|
||||||
DolphinEventTypeFlush,
|
DolphinEventTypeFlush,
|
||||||
DolphinEventTypeIncreaseButthurt,
|
DolphinEventTypeLevel,
|
||||||
DolphinEventTypeClearLimits,
|
|
||||||
} DolphinEventType;
|
} DolphinEventType;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -25,20 +24,11 @@ typedef struct {
|
||||||
} DolphinEvent;
|
} DolphinEvent;
|
||||||
|
|
||||||
struct Dolphin {
|
struct Dolphin {
|
||||||
// State
|
|
||||||
DolphinState* state;
|
DolphinState* state;
|
||||||
// Queue
|
|
||||||
FuriMessageQueue* event_queue;
|
|
||||||
FuriPubSub* pubsub;
|
FuriPubSub* pubsub;
|
||||||
FuriTimer* butthurt_timer;
|
FuriMessageQueue* event_queue;
|
||||||
FuriTimer* flush_timer;
|
FuriEventLoop* event_loop;
|
||||||
FuriTimer* clear_limits_timer;
|
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);
|
|
||||||
|
|
|
@ -5,32 +5,14 @@
|
||||||
#include "check.h"
|
#include "check.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
|
|
||||||
#include <m-bptree.h>
|
|
||||||
#include <m-i-list.h>
|
|
||||||
|
|
||||||
#include <FreeRTOS.h>
|
#include <FreeRTOS.h>
|
||||||
#include <task.h>
|
#include <task.h>
|
||||||
|
|
||||||
#define TAG "FuriEventLoop"
|
#define TAG "FuriEventLoop"
|
||||||
|
|
||||||
struct FuriEventLoopItem {
|
/*
|
||||||
// Source
|
* Private functions
|
||||||
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)
|
|
||||||
|
|
||||||
static FuriEventLoopItem* furi_event_loop_item_alloc(
|
static FuriEventLoopItem* furi_event_loop_item_alloc(
|
||||||
FuriEventLoop* owner,
|
FuriEventLoop* owner,
|
||||||
|
@ -47,56 +29,17 @@ static void furi_event_loop_item_set_callback(
|
||||||
|
|
||||||
static void furi_event_loop_item_notify(FuriEventLoopItem* instance);
|
static void furi_event_loop_item_notify(FuriEventLoopItem* instance);
|
||||||
|
|
||||||
/* Event Loop RB tree */
|
static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) {
|
||||||
#define FURI_EVENT_LOOP_TREE_RANK (4)
|
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,
|
* Main public API
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
FuriEventLoop* furi_event_loop_alloc(void) {
|
FuriEventLoop* furi_event_loop_alloc(void) {
|
||||||
FuriEventLoop* instance = malloc(sizeof(FuriEventLoop));
|
FuriEventLoop* instance = malloc(sizeof(FuriEventLoop));
|
||||||
|
@ -105,6 +48,9 @@ FuriEventLoop* furi_event_loop_alloc(void) {
|
||||||
|
|
||||||
FuriEventLoopTree_init(instance->tree);
|
FuriEventLoopTree_init(instance->tree);
|
||||||
WaitingList_init(instance->waiting_list);
|
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
|
// Clear notification state and value
|
||||||
xTaskNotifyStateClearIndexed(instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX);
|
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) {
|
void furi_event_loop_free(FuriEventLoop* instance) {
|
||||||
furi_check(instance);
|
furi_check(instance);
|
||||||
furi_check(instance->thread_id == furi_thread_get_current_id());
|
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);
|
FuriEventLoopTree_clear(instance->tree);
|
||||||
|
PendingQueue_clear(instance->pending_queue);
|
||||||
|
|
||||||
uint32_t flags = 0;
|
uint32_t flags = 0;
|
||||||
BaseType_t ret = xTaskNotifyWaitIndexed(
|
BaseType_t ret = xTaskNotifyWaitIndexed(
|
||||||
FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0, FuriEventLoopFlagAll, &flags, 0);
|
FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0, FuriEventLoopFlagAll, &flags, 0);
|
||||||
if(ret == pdTRUE) {
|
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);
|
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) {
|
void furi_event_loop_run(FuriEventLoop* instance) {
|
||||||
furi_check(instance);
|
furi_check(instance);
|
||||||
furi_check(instance->thread_id == furi_thread_get_current_id());
|
furi_check(instance->thread_id == furi_thread_get_current_id());
|
||||||
|
|
||||||
|
furi_event_loop_init_tick(instance);
|
||||||
|
|
||||||
furi_thread_set_signal_callback(
|
furi_thread_set_signal_callback(
|
||||||
instance->thread_id, furi_event_loop_signal_callback, instance);
|
instance->thread_id, furi_event_loop_signal_callback, instance);
|
||||||
|
|
||||||
uint32_t timeout = instance->tick_callback ? instance->tick_interval : FuriWaitForever;
|
|
||||||
|
|
||||||
while(true) {
|
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;
|
uint32_t flags = 0;
|
||||||
BaseType_t ret = xTaskNotifyWaitIndexed(
|
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;
|
instance->state = FuriEventLoopStateProcessing;
|
||||||
|
|
||||||
if(ret == pdTRUE) {
|
if(ret == pdTRUE) {
|
||||||
if(flags & FuriEventLoopFlagStop) {
|
if(flags & FuriEventLoopFlagStop) {
|
||||||
instance->state = FuriEventLoopStateIdle;
|
instance->state = FuriEventLoopStateStopped;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} else if(flags & FuriEventLoopFlagEvent) {
|
} else if(flags & FuriEventLoopFlagEvent) {
|
||||||
FuriEventLoopItem* item = NULL;
|
FuriEventLoopItem* item = NULL;
|
||||||
FURI_CRITICAL_ENTER();
|
FURI_CRITICAL_ENTER();
|
||||||
|
|
||||||
if(!WaitingList_empty_p(instance->waiting_list)) {
|
if(!WaitingList_empty_p(instance->waiting_list)) {
|
||||||
item = WaitingList_pop_front(instance->waiting_list);
|
item = WaitingList_pop_front(instance->waiting_list);
|
||||||
WaitingList_init_field(item);
|
WaitingList_init_field(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
FURI_CRITICAL_EXIT();
|
FURI_CRITICAL_EXIT();
|
||||||
|
|
||||||
if(item) {
|
if(item) {
|
||||||
while(true) {
|
while(true) {
|
||||||
FuriEventLoopProcessStatus ret =
|
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) {
|
} else if(!furi_event_loop_process_expired_timers(instance)) {
|
||||||
instance->tick_callback(instance->tick_callback_context);
|
furi_event_loop_process_tick(instance);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
instance->state = FuriEventLoopStateIdle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
furi_thread_set_signal_callback(instance->thread_id, NULL, NULL);
|
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);
|
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,
|
FuriEventLoop* instance,
|
||||||
uint32_t interval,
|
FuriEventLoopPendingCallback callback,
|
||||||
FuriEventLoopTickCallback callback,
|
|
||||||
void* context) {
|
void* context) {
|
||||||
furi_check(instance);
|
furi_check(instance);
|
||||||
furi_check(instance->thread_id == furi_thread_get_current_id());
|
furi_check(instance->thread_id == furi_thread_get_current_id());
|
||||||
furi_check(callback ? interval > 0 : true);
|
furi_check(callback);
|
||||||
|
|
||||||
instance->tick_interval = interval;
|
const FuriEventLoopPendingQueueItem item = {
|
||||||
instance->tick_callback = callback;
|
.callback = callback,
|
||||||
instance->tick_callback_context = context;
|
.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(
|
void furi_event_loop_message_queue_subscribe(
|
||||||
FuriEventLoop* instance,
|
FuriEventLoop* instance,
|
||||||
FuriMessageQueue* message_queue,
|
FuriMessageQueue* message_queue,
|
||||||
|
@ -230,7 +222,7 @@ void furi_event_loop_message_queue_subscribe(
|
||||||
void* context) {
|
void* context) {
|
||||||
furi_check(instance);
|
furi_check(instance);
|
||||||
furi_check(instance->thread_id == furi_thread_get_current_id());
|
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_check(message_queue);
|
||||||
|
|
||||||
FURI_CRITICAL_ENTER();
|
FURI_CRITICAL_ENTER();
|
||||||
|
@ -267,7 +259,7 @@ void furi_event_loop_message_queue_unsubscribe(
|
||||||
FuriEventLoop* instance,
|
FuriEventLoop* instance,
|
||||||
FuriMessageQueue* message_queue) {
|
FuriMessageQueue* message_queue) {
|
||||||
furi_check(instance);
|
furi_check(instance);
|
||||||
furi_check(instance->state == FuriEventLoopStateIdle);
|
furi_check(instance->state == FuriEventLoopStateStopped);
|
||||||
furi_check(instance->thread_id == furi_thread_get_current_id());
|
furi_check(instance->thread_id == furi_thread_get_current_id());
|
||||||
|
|
||||||
FURI_CRITICAL_ENTER();
|
FURI_CRITICAL_ENTER();
|
||||||
|
|
|
@ -34,7 +34,7 @@ typedef struct FuriEventLoop FuriEventLoop;
|
||||||
* Couple things to keep in mind:
|
* Couple things to keep in mind:
|
||||||
* - You can have 1 event_loop per 1 thread
|
* - You can have 1 event_loop per 1 thread
|
||||||
* - You can not use event_loop instance in the other 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
|
* @return The Event Loop instance
|
||||||
*/
|
*/
|
||||||
|
@ -72,8 +72,10 @@ typedef void (*FuriEventLoopTickCallback)(void* context);
|
||||||
|
|
||||||
/** Set Event Loop tick callback
|
/** Set Event Loop tick callback
|
||||||
*
|
*
|
||||||
* Tick callback called after specified inactivity time. It's not periodic. If
|
* Tick callback is called periodically after specified inactivity time.
|
||||||
* Event Loop is busy then ticks will be skipped.
|
* 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 instance The Event Loop instance
|
||||||
* @param[in] interval The tick interval
|
* @param[in] interval The tick interval
|
||||||
|
@ -86,6 +88,32 @@ void furi_event_loop_tick_set(
|
||||||
FuriEventLoopTickCallback callback,
|
FuriEventLoopTickCallback callback,
|
||||||
void* context);
|
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
|
* Message queue related APIs
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,35 +1,97 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "event_loop.h"
|
#include "event_loop.h"
|
||||||
|
#include "event_loop_link_i.h"
|
||||||
|
#include "event_loop_timer_i.h"
|
||||||
|
#include "event_loop_tick_i.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#include <m-list.h>
|
||||||
extern "C" {
|
#include <m-bptree.h>
|
||||||
#endif
|
#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 {
|
typedef struct {
|
||||||
FuriEventLoopItem* item_in;
|
FuriEventLoopPendingCallback callback;
|
||||||
FuriEventLoopItem* item_out;
|
void* context;
|
||||||
} FuriEventLoopLink;
|
} 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 {
|
// Active timer list
|
||||||
const FuriEventLoopContractGetLink get_link;
|
TimerList_t timer_list;
|
||||||
const FuriEventLoopContractGetLevel get_level;
|
// Timer request queue
|
||||||
} FuriEventLoopContract;
|
TimerQueue_t timer_queue;
|
||||||
|
// Pending callback queue
|
||||||
bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context);
|
PendingQueue_t pending_queue;
|
||||||
|
// Tick event
|
||||||
#ifdef __cplusplus
|
FuriEventLoopTick tick;
|
||||||
}
|
};
|
||||||
#endif
|
|
||||||
|
|
35
furi/core/event_loop_link_i.h
Normal file
35
furi/core/event_loop_link_i.h
Normal 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
|
69
furi/core/event_loop_tick.c
Normal file
69
furi/core/event_loop_tick.c
Normal 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();
|
||||||
|
}
|
16
furi/core/event_loop_tick_i.h
Normal file
16
furi/core/event_loop_tick_i.h
Normal 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);
|
215
furi/core/event_loop_timer.c
Normal file
215
furi/core/event_loop_timer.c
Normal 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;
|
||||||
|
}
|
118
furi/core/event_loop_timer.h
Normal file
118
furi/core/event_loop_timer.h
Normal 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
|
43
furi/core/event_loop_timer_i.h
Normal file
43
furi/core/event_loop_timer_i.h
Normal 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);
|
|
@ -1,5 +1,11 @@
|
||||||
#include "message_queue_i.h"
|
#include "message_queue_i.h"
|
||||||
|
|
||||||
|
#include <FreeRTOS.h>
|
||||||
|
#include <queue.h>
|
||||||
|
|
||||||
|
#include "kernel.h"
|
||||||
|
#include "check.h"
|
||||||
|
|
||||||
// Internal FreeRTOS member names
|
// Internal FreeRTOS member names
|
||||||
#define uxMessagesWaiting uxDummy4[0]
|
#define uxMessagesWaiting uxDummy4[0]
|
||||||
#define uxLength uxDummy4[1]
|
#define uxLength uxDummy4[1]
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "message_queue.h"
|
#include "message_queue.h"
|
||||||
|
#include "event_loop_link_i.h"
|
||||||
#include "kernel.h"
|
|
||||||
#include "event_loop_i.h"
|
|
||||||
#include "check.h"
|
|
||||||
|
|
||||||
#include <FreeRTOS.h>
|
|
||||||
#include <queue.h>
|
|
||||||
|
|
||||||
extern const FuriEventLoopContract furi_message_queue_event_loop_contract;
|
extern const FuriEventLoopContract furi_message_queue_event_loop_contract;
|
|
@ -5,6 +5,7 @@
|
||||||
#include "core/check.h"
|
#include "core/check.h"
|
||||||
#include "core/common_defines.h"
|
#include "core/common_defines.h"
|
||||||
#include "core/event_loop.h"
|
#include "core/event_loop.h"
|
||||||
|
#include "core/event_loop_timer.h"
|
||||||
#include "core/event_flag.h"
|
#include "core/event_flag.h"
|
||||||
#include "core/kernel.h"
|
#include "core/kernel.h"
|
||||||
#include "core/log.h"
|
#include "core/log.h"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
entry,status,name,type,params
|
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.h,,
|
||||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||||
Header,+,applications/services/cli/cli.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_get,uint32_t,FuriEventFlag*
|
||||||
Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t"
|
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_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t"
|
||||||
Function,-,furi_event_loop_alloc,FuriEventLoop*,
|
Function,+,furi_event_loop_alloc,FuriEventLoop*,
|
||||||
Function,-,furi_event_loop_free,void,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_subscribe,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopMessageQueueCallback, void*"
|
||||||
Function,-,furi_event_loop_message_queue_unsubscribe,void,"FuriEventLoop*, FuriMessageQueue*"
|
Function,+,furi_event_loop_message_queue_unsubscribe,void,"FuriEventLoop*, FuriMessageQueue*"
|
||||||
Function,-,furi_event_loop_run,void,FuriEventLoop*
|
Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*"
|
||||||
Function,-,furi_event_loop_stop,void,FuriEventLoop*
|
Function,+,furi_event_loop_run,void,FuriEventLoop*
|
||||||
Function,-,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*"
|
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_get_tick,uint32_t,
|
||||||
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
|
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
|
||||||
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*
|
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*
|
||||||
|
|
|
|
@ -1,5 +1,5 @@
|
||||||
entry,status,name,type,params
|
entry,status,name,type,params
|
||||||
Version,+,67.1,,
|
Version,+,67.2,,
|
||||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
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.h,,
|
||||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.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_get,uint32_t,FuriEventFlag*
|
||||||
Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t"
|
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_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t"
|
||||||
Function,-,furi_event_loop_alloc,FuriEventLoop*,
|
Function,+,furi_event_loop_alloc,FuriEventLoop*,
|
||||||
Function,-,furi_event_loop_free,void,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_subscribe,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopMessageQueueCallback, void*"
|
||||||
Function,-,furi_event_loop_message_queue_unsubscribe,void,"FuriEventLoop*, FuriMessageQueue*"
|
Function,+,furi_event_loop_message_queue_unsubscribe,void,"FuriEventLoop*, FuriMessageQueue*"
|
||||||
Function,-,furi_event_loop_run,void,FuriEventLoop*
|
Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*"
|
||||||
Function,-,furi_event_loop_stop,void,FuriEventLoop*
|
Function,+,furi_event_loop_run,void,FuriEventLoop*
|
||||||
Function,-,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*"
|
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_get_tick,uint32_t,
|
||||||
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
|
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
|
||||||
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*
|
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*
|
||||||
|
|
|
Loading…
Reference in a new issue