1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-12 04:34:43 +04: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:
Georgii Surkov
2024-07-02 15:09:50 +03:00
committed by GitHub
parent bf90843f25
commit 139660d206
20 changed files with 1087 additions and 256 deletions

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