diff --git a/applications/services/notification/notification_messages.c b/applications/services/notification/notification_messages.c index 8b79162264..3dc1546546 100644 --- a/applications/services/notification/notification_messages.c +++ b/applications/services/notification/notification_messages.c @@ -593,3 +593,7 @@ const NotificationSequence sequence_lcd_contrast_update = { &message_lcd_contrast_update, NULL, }; + +const NotificationSequence sequence_empty = { + NULL, +}; diff --git a/applications/services/notification/notification_messages.h b/applications/services/notification/notification_messages.h index 873bb37a86..3960d93b7c 100644 --- a/applications/services/notification/notification_messages.h +++ b/applications/services/notification/notification_messages.h @@ -145,6 +145,9 @@ extern const NotificationSequence sequence_audiovisual_alert; // LCD extern const NotificationSequence sequence_lcd_contrast_update; +// Wait for notification queue become empty +extern const NotificationSequence sequence_empty; + #ifdef __cplusplus } #endif diff --git a/applications/settings/application.fam b/applications/settings/application.fam index cc4b9703dc..1d6db35a7b 100644 --- a/applications/settings/application.fam +++ b/applications/settings/application.fam @@ -5,6 +5,7 @@ App( provides=[ "passport", "system_settings", + "clock_settings", "about", ], ) diff --git a/applications/settings/clock_settings/application.fam b/applications/settings/clock_settings/application.fam new file mode 100644 index 0000000000..206848aa3e --- /dev/null +++ b/applications/settings/clock_settings/application.fam @@ -0,0 +1,17 @@ +App( + appid="clock_settings", + name="Clock & Alarm", + apptype=FlipperAppType.SETTINGS, + entry_point="clock_settings", + requires=["gui"], + provides=["clock_settings_start"], + stack_size=1 * 1024, + order=90, +) + +App( + appid="clock_settings_start", + apptype=FlipperAppType.STARTUP, + entry_point="clock_settings_start", + order=1000, +) diff --git a/applications/settings/clock_settings/clock_settings.c b/applications/settings/clock_settings/clock_settings.c new file mode 100644 index 0000000000..455f1deea3 --- /dev/null +++ b/applications/settings/clock_settings/clock_settings.c @@ -0,0 +1,71 @@ +#include "clock_settings.h" + +#include +#include + +static bool clock_settings_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + ClockSettings* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool clock_settings_back_event_callback(void* context) { + furi_assert(context); + ClockSettings* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +ClockSettings* clock_settings_alloc() { + ClockSettings* app = malloc(sizeof(ClockSettings)); + + app->gui = furi_record_open(RECORD_GUI); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&clock_settings_scene_handlers, app); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, clock_settings_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, clock_settings_back_event_callback); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->pwm_view = + clock_settings_module_alloc(view_dispatcher_get_event_loop(app->view_dispatcher)); + view_dispatcher_add_view( + app->view_dispatcher, ClockSettingsViewPwm, clock_settings_module_get_view(app->pwm_view)); + + scene_manager_next_scene(app->scene_manager, ClockSettingsSceneStart); + + return app; +} + +void clock_settings_free(ClockSettings* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, ClockSettingsViewPwm); + + clock_settings_module_free(app->pwm_view); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t clock_settings(void* p) { + UNUSED(p); + ClockSettings* clock_settings = clock_settings_alloc(); + + view_dispatcher_run(clock_settings->view_dispatcher); + + clock_settings_free(clock_settings); + + return 0; +} diff --git a/applications/settings/clock_settings/clock_settings.h b/applications/settings/clock_settings/clock_settings.h new file mode 100644 index 0000000000..db404ec8eb --- /dev/null +++ b/applications/settings/clock_settings/clock_settings.h @@ -0,0 +1,31 @@ +#pragma once + +#include "scenes/clock_settings_scene.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "views/clock_settings_module.h" + +typedef struct ClockSettings ClockSettings; + +struct ClockSettings { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + ClockSettingsModule* pwm_view; +}; + +typedef enum { + ClockSettingsViewPwm, +} ClockSettingsView; + +typedef enum { + ClockSettingsCustomEventNone, +} ClockSettingsCustomEvent; diff --git a/applications/settings/clock_settings/clock_settings_alarm.c b/applications/settings/clock_settings/clock_settings_alarm.c new file mode 100644 index 0000000000..7b096ef706 --- /dev/null +++ b/applications/settings/clock_settings/clock_settings_alarm.c @@ -0,0 +1,177 @@ +#include +#include + +#include +#include + +#include +#include + +#include + +#define TAG "ClockSettingsAlarm" + +typedef struct { + DateTime now; + IconAnimation* icon; +} ClockSettingsAlramModel; + +const NotificationSequence sequence_alarm = { + &message_force_speaker_volume_setting_1f, + &message_force_vibro_setting_on, + &message_force_display_brightness_setting_1f, + &message_vibro_on, + + &message_display_backlight_on, + &message_note_c7, + &message_delay_250, + + &message_display_backlight_off, + &message_note_c4, + &message_delay_250, + + &message_display_backlight_on, + &message_note_c7, + &message_delay_250, + + &message_display_backlight_off, + &message_note_c4, + &message_delay_250, + + &message_sound_off, + &message_vibro_off, + NULL, +}; + +static void clock_settings_alarm_draw_callback(Canvas* canvas, void* ctx) { + ClockSettingsAlramModel* model = ctx; + char buffer[64] = {}; + + canvas_draw_icon_animation(canvas, 5, 6, model->icon); + + canvas_set_font(canvas, FontBigNumbers); + snprintf(buffer, sizeof(buffer), "%02u:%02u", model->now.hour, model->now.minute); + canvas_draw_str(canvas, 58, 32, buffer); + + canvas_set_font(canvas, FontPrimary); + snprintf( + buffer, + sizeof(buffer), + "%02u.%02u.%04u", + model->now.day, + model->now.month, + model->now.year); + canvas_draw_str(canvas, 60, 44, buffer); +} + +static void clock_settings_alarm_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +void clock_settings_alarm_animation_callback(IconAnimation* instance, void* context) { + UNUSED(instance); + ViewPort* view_port = context; + view_port_update(view_port); +} + +int32_t clock_settings_alarm(void* p) { + UNUSED(p); + + // View Model + ClockSettingsAlramModel model; + + furi_hal_rtc_get_datetime(&model.now); + model.icon = icon_animation_alloc(&A_Alarm_47x39); + + // Alloc message queue + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + // Configure view port + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, clock_settings_alarm_draw_callback, &model); + view_port_input_callback_set(view_port, clock_settings_alarm_input_callback, event_queue); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_alarm); + + icon_animation_set_update_callback( + model.icon, clock_settings_alarm_animation_callback, view_port); + icon_animation_start(model.icon); + + // Process events + InputEvent event; + bool running = true; + while(running) { + if(furi_message_queue_get(event_queue, &event, 2000) == FuriStatusOk) { + if(event.type == InputTypePress) { + running = false; + } + } else { + notification_message(notification, &sequence_alarm); + furi_hal_rtc_get_datetime(&model.now); + view_port_update(view_port); + } + } + + icon_animation_stop(model.icon); + + notification_message_block(notification, &sequence_empty); + furi_record_close(RECORD_NOTIFICATION); + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_record_close(RECORD_GUI); + + icon_animation_free(model.icon); + + return 0; +} + +FuriThread* clock_settings_alarm_thread = NULL; + +static void clock_settings_alarm_thread_state_callback( + FuriThread* thread, + FuriThreadState state, + void* context) { + furi_assert(clock_settings_alarm_thread == thread); + UNUSED(context); + + if(state == FuriThreadStateStopped) { + furi_thread_free(thread); + clock_settings_alarm_thread = NULL; + } +} + +static void clock_settings_alarm_start(void* context, uint32_t arg) { + UNUSED(context); + UNUSED(arg); + + FURI_LOG_I(TAG, "spawning alarm thread"); + + if(clock_settings_alarm_thread) return; + + clock_settings_alarm_thread = + furi_thread_alloc_ex("ClockAlarm", 1024, clock_settings_alarm, NULL); + furi_thread_set_state_callback( + clock_settings_alarm_thread, clock_settings_alarm_thread_state_callback); + furi_thread_start(clock_settings_alarm_thread); +} + +static void clock_settings_alarm_isr(void* context) { + UNUSED(context); + furi_timer_pending_callback(clock_settings_alarm_start, NULL, 0); +} + +void clock_settings_start(void) { +#ifndef FURI_RAM_EXEC + furi_hal_rtc_set_alarm_callback(clock_settings_alarm_isr, NULL); +#endif +} diff --git a/applications/settings/clock_settings/scenes/clock_settings_scene.c b/applications/settings/clock_settings/scenes/clock_settings_scene.c new file mode 100644 index 0000000000..13a1c3395e --- /dev/null +++ b/applications/settings/clock_settings/scenes/clock_settings_scene.c @@ -0,0 +1,30 @@ +#include "../clock_settings.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const clock_settings_scene_on_enter_handlers[])(void*) = { +#include "clock_settings_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const clock_settings_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "clock_settings_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const clock_settings_scene_on_exit_handlers[])(void* context) = { +#include "clock_settings_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers clock_settings_scene_handlers = { + .on_enter_handlers = clock_settings_scene_on_enter_handlers, + .on_event_handlers = clock_settings_scene_on_event_handlers, + .on_exit_handlers = clock_settings_scene_on_exit_handlers, + .scene_num = ClockSettingsSceneNum, +}; diff --git a/applications/settings/clock_settings/scenes/clock_settings_scene.h b/applications/settings/clock_settings/scenes/clock_settings_scene.h new file mode 100644 index 0000000000..d0582252c4 --- /dev/null +++ b/applications/settings/clock_settings/scenes/clock_settings_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) ClockSettingsScene##id, +typedef enum { +#include "clock_settings_scene_config.h" + ClockSettingsSceneNum, +} ClockSettingsScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers clock_settings_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "clock_settings_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "clock_settings_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "clock_settings_scene_config.h" +#undef ADD_SCENE diff --git a/applications/settings/clock_settings/scenes/clock_settings_scene_config.h b/applications/settings/clock_settings/scenes/clock_settings_scene_config.h new file mode 100644 index 0000000000..496b3f7af8 --- /dev/null +++ b/applications/settings/clock_settings/scenes/clock_settings_scene_config.h @@ -0,0 +1 @@ +ADD_SCENE(clock_settings, start, Start) diff --git a/applications/settings/clock_settings/scenes/clock_settings_scene_start.c b/applications/settings/clock_settings/scenes/clock_settings_scene_start.c new file mode 100644 index 0000000000..81cf58a748 --- /dev/null +++ b/applications/settings/clock_settings/scenes/clock_settings_scene_start.c @@ -0,0 +1,32 @@ +#include "../clock_settings.h" +#include + +#define TAG "SceneStart" + +typedef enum { + SubmenuIndexPwm, + SubmenuIndexClockOutput, +} SubmenuIndex; + +void clock_settings_scene_start_submenu_callback(void* context, uint32_t index) { + ClockSettings* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void clock_settings_scene_start_on_enter(void* context) { + ClockSettings* app = context; + + view_dispatcher_switch_to_view(app->view_dispatcher, ClockSettingsViewPwm); +} + +bool clock_settings_scene_start_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + + return false; +} + +void clock_settings_scene_start_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/settings/clock_settings/views/clock_settings_module.c b/applications/settings/clock_settings/views/clock_settings_module.c new file mode 100644 index 0000000000..9ab5773a5e --- /dev/null +++ b/applications/settings/clock_settings/views/clock_settings_module.c @@ -0,0 +1,438 @@ +#include "clock_settings_module.h" + +#include +#include +#include + +#define TAG "ClockSettingsModule" + +struct ClockSettingsModule { + FuriEventLoopTimer* timer; + View* view; +}; + +typedef struct { + DateTime current; + DateTime alarm; + bool alarm_enabled; + bool editing; + + uint8_t row; + uint8_t column; +} ClockSettingsModuleViewModel; + +typedef enum { + EditStateNone, + EditStateActive, + EditStateActiveEditing, +} EditState; + +#define get_state(m, r, c) \ + ((m)->row == (r) && (m)->column == (c) ? \ + ((m)->editing ? EditStateActiveEditing : EditStateActive) : \ + EditStateNone) + +#define ROW_0_Y (4) +#define ROW_0_H (20) + +#define ROW_1_Y (30) +#define ROW_1_H (12) + +#define ROW_2_Y (48) +#define ROW_2_H (12) + +#define ROW_COUNT 3 +#define COLUMN_COUNT 3 + +static inline void clock_settings_module_cleanup_date(DateTime* dt) { + uint8_t day_per_month = + datetime_get_days_per_month(datetime_is_leap_year(dt->year), dt->month); + if(dt->day > day_per_month) { + dt->day = day_per_month; + } +} + +static inline void clock_settings_module_draw_block( + Canvas* canvas, + int32_t x, + int32_t y, + size_t w, + size_t h, + Font font, + EditState state, + const char* text) { + canvas_set_color(canvas, ColorBlack); + if(state != EditStateNone) { + if(state == EditStateActiveEditing) { + canvas_draw_icon(canvas, x + w / 2 - 2, y - 1 - 3, &I_SmallArrowUp_3x5); + canvas_draw_icon(canvas, x + w / 2 - 2, y + h + 1, &I_SmallArrowDown_3x5); + } + canvas_draw_rbox(canvas, x, y, w, h, 1); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_draw_rframe(canvas, x, y, w, h, 1); + } + + canvas_set_font(canvas, font); + canvas_draw_str_aligned(canvas, x + w / 2, y + h / 2, AlignCenter, AlignCenter, text); + if(state != EditStateNone) { + canvas_set_color(canvas, ColorBlack); + } +} + +static void + clock_settings_module_draw_time_callback(Canvas* canvas, ClockSettingsModuleViewModel* model) { + char buffer[64]; + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, ROW_0_Y + 15, "Time"); + + snprintf(buffer, sizeof(buffer), "%02u", model->current.hour); + clock_settings_module_draw_block( + canvas, 32, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 0), buffer); + canvas_draw_box(canvas, 62, ROW_0_Y + ROW_0_H - 7, 2, 2); + canvas_draw_box(canvas, 62, ROW_0_Y + ROW_0_H - 7 - 6, 2, 2); + + snprintf(buffer, sizeof(buffer), "%02u", model->current.minute); + clock_settings_module_draw_block( + canvas, 66, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 1), buffer); + canvas_draw_box(canvas, 96, ROW_0_Y + ROW_0_H - 7, 2, 2); + canvas_draw_box(canvas, 96, ROW_0_Y + ROW_0_H - 7 - 6, 2, 2); + + snprintf(buffer, sizeof(buffer), "%02u", model->current.second); + clock_settings_module_draw_block( + canvas, 100, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 2), buffer); +} + +static void + clock_settings_module_draw_date_callback(Canvas* canvas, ClockSettingsModuleViewModel* model) { + char buffer[64]; + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, ROW_1_Y + 9, "Date"); + // Day + snprintf(buffer, sizeof(buffer), "%02u", model->current.day); + clock_settings_module_draw_block( + canvas, 44, ROW_1_Y, 17, ROW_1_H, FontPrimary, get_state(model, 1, 0), buffer); + canvas_draw_box(canvas, 71 - 6, ROW_1_Y + ROW_1_H - 4, 2, 2); + // Month + snprintf(buffer, sizeof(buffer), "%02u", model->current.month); + clock_settings_module_draw_block( + canvas, 71, ROW_1_Y, 17, ROW_1_H, FontPrimary, get_state(model, 1, 1), buffer); + canvas_draw_box(canvas, 98 - 6, ROW_1_Y + ROW_1_H - 4, 2, 2); + // Year + snprintf(buffer, sizeof(buffer), "%04u", model->current.year); + clock_settings_module_draw_block( + canvas, 98, ROW_1_Y, 30, ROW_1_H, FontPrimary, get_state(model, 1, 2), buffer); +} + +static void + clock_settings_module_draw_alarm_callback(Canvas* canvas, ClockSettingsModuleViewModel* model) { + char buffer[64]; + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, ROW_2_Y + 9, "Alarm"); + + snprintf(buffer, sizeof(buffer), "%02u", model->alarm.hour); + clock_settings_module_draw_block( + canvas, 58, ROW_2_Y, 17, ROW_2_H, FontPrimary, get_state(model, 2, 0), buffer); + canvas_draw_box(canvas, 81 - 4, ROW_2_Y + ROW_2_H - 4, 2, 2); + canvas_draw_box(canvas, 81 - 4, ROW_2_Y + ROW_2_H - 4 - 4, 2, 2); + + snprintf(buffer, sizeof(buffer), "%02u", model->alarm.minute); + clock_settings_module_draw_block( + canvas, 81, ROW_2_Y, 17, ROW_2_H, FontPrimary, get_state(model, 2, 1), buffer); + + clock_settings_module_draw_block( + canvas, + 106, + ROW_2_Y, + 22, + ROW_2_H, + FontPrimary, + get_state(model, 2, 2), + model->alarm_enabled ? "On" : "Off"); +} + +static void clock_settings_module_draw_callback(Canvas* canvas, void* _model) { + ClockSettingsModuleViewModel* model = _model; + clock_settings_module_draw_time_callback(canvas, model); + clock_settings_module_draw_date_callback(canvas, model); + clock_settings_module_draw_alarm_callback(canvas, model); +} + +static bool clock_settings_module_input_navigation_callback( + InputEvent* event, + ClockSettingsModuleViewModel* model) { + if(event->key == InputKeyUp) { + if(model->row > 0) model->row--; + } else if(event->key == InputKeyDown) { + if(model->row < ROW_COUNT - 1) model->row++; + } else if(event->key == InputKeyOk) { + model->editing = !model->editing; + } else if(event->key == InputKeyRight) { + if(model->column < COLUMN_COUNT - 1) model->column++; + } else if(event->key == InputKeyLeft) { + if(model->column > 0) model->column--; + } else if(event->key == InputKeyBack && model->editing) { + model->editing = false; + } else { + return false; + } + + return true; +} + +static bool clock_settings_module_input_time_callback( + InputEvent* event, + ClockSettingsModuleViewModel* model) { + if(event->key == InputKeyUp) { + if(model->column == 0) { + model->current.hour++; + model->current.hour = model->current.hour % 24; + } else if(model->column == 1) { + model->current.minute++; + model->current.minute = model->current.minute % 60; + } else if(model->column == 2) { + model->current.second++; + model->current.second = model->current.second % 60; + } else { + furi_crash(); + } + } else if(event->key == InputKeyDown) { + if(model->column == 0) { + if(model->current.hour > 0) { + model->current.hour--; + } else { + model->current.hour = 23; + } + model->current.hour = model->current.hour % 24; + } else if(model->column == 1) { + if(model->current.minute > 0) { + model->current.minute--; + } else { + model->current.minute = 59; + } + model->current.minute = model->current.minute % 60; + } else if(model->column == 2) { + if(model->current.second > 0) { + model->current.second--; + } else { + model->current.second = 59; + } + model->current.second = model->current.second % 60; + } else { + furi_crash(); + } + } else { + return clock_settings_module_input_navigation_callback(event, model); + } + + return true; +} + +static bool clock_settings_module_input_date_callback( + InputEvent* event, + ClockSettingsModuleViewModel* model) { + if(event->key == InputKeyUp) { + if(model->column == 0) { + if(model->current.day < 31) model->current.day++; + } else if(model->column == 1) { + if(model->current.month < 12) { + model->current.month++; + } + } else if(model->column == 2) { + if(model->current.year < 2099) { + model->current.year++; + } + } else { + furi_crash(); + } + } else if(event->key == InputKeyDown) { + if(model->column == 0) { + if(model->current.day > 1) { + model->current.day--; + } + } else if(model->column == 1) { + if(model->current.month > 1) { + model->current.month--; + } + } else if(model->column == 2) { + if(model->current.year > 2000) { + model->current.year--; + } + } else { + furi_crash(); + } + } else { + return clock_settings_module_input_navigation_callback(event, model); + } + + clock_settings_module_cleanup_date(&model->current); + + return true; +} + +static bool clock_settings_module_input_alarm_callback( + InputEvent* event, + ClockSettingsModuleViewModel* model) { + if(event->key == InputKeyUp) { + if(model->column == 0) { + model->alarm.hour++; + model->alarm.hour = model->alarm.hour % 24; + } else if(model->column == 1) { + model->alarm.minute++; + model->alarm.minute = model->alarm.minute % 60; + } else if(model->column == 2) { + model->alarm_enabled = !model->alarm_enabled; + } else { + furi_crash(); + } + } else if(event->key == InputKeyDown) { + if(model->column == 0) { + if(model->alarm.hour > 0) { + model->alarm.hour--; + } else { + model->alarm.hour = 23; + } + model->alarm.hour = model->alarm.hour % 24; + } else if(model->column == 1) { + if(model->alarm.minute > 0) { + model->alarm.minute--; + } else { + model->alarm.minute = 59; + } + model->alarm.minute = model->alarm.minute % 60; + } else if(model->column == 2) { + model->alarm_enabled = !model->alarm_enabled; + } else { + furi_crash(); + } + } else { + return clock_settings_module_input_navigation_callback(event, model); + } + + return true; +} + +static bool clock_settings_module_input_callback(InputEvent* event, void* context) { + furi_assert(context); + + ClockSettingsModule* instance = context; + bool consumed = false; + + with_view_model( + instance->view, + ClockSettingsModuleViewModel * model, + { + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + bool previous_editing = model->editing; + if(model->editing) { + if(model->row == 0) { + consumed = clock_settings_module_input_time_callback(event, model); + } else if(model->row == 1) { + consumed = clock_settings_module_input_date_callback(event, model); + } else if(model->row == 2) { + consumed = clock_settings_module_input_alarm_callback(event, model); + } else { + furi_crash(); + } + } else { + consumed = clock_settings_module_input_navigation_callback(event, model); + } + + // Switching between navigate/edit + if(model->editing != previous_editing) { + if(model->row == 2) { + if(!model->editing) { + // Disable alarm + furi_hal_rtc_set_alarm(NULL, false); + // Set new alarm + furi_hal_rtc_set_alarm(&model->alarm, model->alarm_enabled); + // Confirm + model->alarm_enabled = furi_hal_rtc_get_alarm(&model->alarm); + } + } else { + if(model->editing) { + // stop timer to prevent mess with current date time + furi_event_loop_timer_stop(instance->timer); + } else { + // save date time and restart timer + furi_hal_rtc_set_datetime(&model->current); + furi_event_loop_timer_start(instance->timer, 1000); + } + } + } + } + }, + true); + + return consumed; +} + +static void clock_settings_module_timer_callback(void* context) { + furi_assert(context); + ClockSettingsModule* instance = context; + + DateTime dt; + furi_hal_rtc_get_datetime(&dt); + with_view_model( + instance->view, ClockSettingsModuleViewModel * model, { model->current = dt; }, true); +} + +static void clock_settings_module_view_enter_callback(void* context) { + furi_assert(context); + ClockSettingsModule* instance = context; + + clock_settings_module_timer_callback(context); + + DateTime alarm; + bool enabled = furi_hal_rtc_get_alarm(&alarm); + + with_view_model( + instance->view, + ClockSettingsModuleViewModel * model, + { + model->alarm = alarm; + model->alarm_enabled = enabled; + }, + true); + + furi_event_loop_timer_start(instance->timer, 1000); +} + +static void clock_settings_module_view_exit_callback(void* context) { + furi_assert(context); + ClockSettingsModule* instance = context; + furi_event_loop_timer_stop(instance->timer); +} + +ClockSettingsModule* clock_settings_module_alloc(FuriEventLoop* event_loop) { + ClockSettingsModule* instance = malloc(sizeof(ClockSettingsModule)); + + instance->timer = furi_event_loop_timer_alloc( + event_loop, clock_settings_module_timer_callback, FuriEventLoopTimerTypePeriodic, instance); + instance->view = view_alloc(); + view_set_enter_callback(instance->view, clock_settings_module_view_enter_callback); + view_set_exit_callback(instance->view, clock_settings_module_view_exit_callback); + view_allocate_model( + instance->view, ViewModelTypeLocking, sizeof(ClockSettingsModuleViewModel)); + with_view_model( + instance->view, ClockSettingsModuleViewModel * model, { model->row = 0; }, false); + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, clock_settings_module_draw_callback); + view_set_input_callback(instance->view, clock_settings_module_input_callback); + + return instance; +} + +void clock_settings_module_free(ClockSettingsModule* instance) { + furi_assert(instance); + view_free(instance->view); + free(instance); +} + +View* clock_settings_module_get_view(ClockSettingsModule* instance) { + furi_assert(instance); + return instance->view; +} diff --git a/applications/settings/clock_settings/views/clock_settings_module.h b/applications/settings/clock_settings/views/clock_settings_module.h new file mode 100644 index 0000000000..1ae1dee0e6 --- /dev/null +++ b/applications/settings/clock_settings/views/clock_settings_module.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +typedef struct ClockSettingsModule ClockSettingsModule; +typedef void (*ClockSettingsModuleViewCallback)( + uint8_t channel_id, + uint32_t freq, + uint8_t duty, + void* context); + +ClockSettingsModule* clock_settings_module_alloc(FuriEventLoop* event_loop); + +void clock_settings_module_free(ClockSettingsModule* instance); + +View* clock_settings_module_get_view(ClockSettingsModule* instance); + +void clock_settings_module_set( + ClockSettingsModule* instance, + const DateTime* datetime, + bool enabled); + +bool clock_settings_module_get(ClockSettingsModule* instance, DateTime* datetime); diff --git a/assets/icons/Settings/Alarm_47x39/frame_0.png b/assets/icons/Settings/Alarm_47x39/frame_0.png new file mode 100644 index 0000000000..5641160369 Binary files /dev/null and b/assets/icons/Settings/Alarm_47x39/frame_0.png differ diff --git a/assets/icons/Settings/Alarm_47x39/frame_1.png b/assets/icons/Settings/Alarm_47x39/frame_1.png new file mode 100644 index 0000000000..c5c58ea91d Binary files /dev/null and b/assets/icons/Settings/Alarm_47x39/frame_1.png differ diff --git a/assets/icons/Settings/Alarm_47x39/frame_2.png b/assets/icons/Settings/Alarm_47x39/frame_2.png new file mode 100644 index 0000000000..b7d338f7d4 Binary files /dev/null and b/assets/icons/Settings/Alarm_47x39/frame_2.png differ diff --git a/assets/icons/Settings/Alarm_47x39/frame_3.png b/assets/icons/Settings/Alarm_47x39/frame_3.png new file mode 100644 index 0000000000..067d55ddd0 Binary files /dev/null and b/assets/icons/Settings/Alarm_47x39/frame_3.png differ diff --git a/assets/icons/Settings/Alarm_47x39/frame_4.png b/assets/icons/Settings/Alarm_47x39/frame_4.png new file mode 100644 index 0000000000..5641160369 Binary files /dev/null and b/assets/icons/Settings/Alarm_47x39/frame_4.png differ diff --git a/assets/icons/Settings/Alarm_47x39/frame_rate b/assets/icons/Settings/Alarm_47x39/frame_rate new file mode 100644 index 0000000000..0cfbf08886 --- /dev/null +++ b/assets/icons/Settings/Alarm_47x39/frame_rate @@ -0,0 +1 @@ +2 diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index 376e9c9ead..77bfa43872 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -242,9 +242,6 @@ static int32_t flipper_application_thread(void* context) { // wait until all notifications from RAM are completed NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); - const NotificationSequence sequence_empty = { - NULL, - }; notification_message_block(notifications, &sequence_empty); furi_record_close(RECORD_NOTIFICATION); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 8b7e0a393b..b5d51a0dd5 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,78.0,, +Version,+,78.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1377,6 +1377,7 @@ Function,-,furi_hal_resources_init_early,void, Function,+,furi_hal_resources_pin_by_name,const GpioPinRecord*,const char* Function,+,furi_hal_resources_pin_by_number,const GpioPinRecord*,uint8_t Function,-,furi_hal_rtc_deinit_early,void, +Function,-,furi_hal_rtc_get_alarm,_Bool,DateTime* Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, Function,+,furi_hal_rtc_get_datetime,void,DateTime* Function,+,furi_hal_rtc_get_fault_data,uint32_t, @@ -1394,8 +1395,11 @@ Function,+,furi_hal_rtc_get_timestamp,uint32_t, Function,-,furi_hal_rtc_init,void, Function,-,furi_hal_rtc_init_early,void, Function,+,furi_hal_rtc_is_flag_set,_Bool,FuriHalRtcFlag +Function,-,furi_hal_rtc_prepare_for_shutdown,void, Function,+,furi_hal_rtc_reset_flag,void,FuriHalRtcFlag Function,+,furi_hal_rtc_reset_registers,void, +Function,-,furi_hal_rtc_set_alarm,void,"const DateTime*, _Bool" +Function,-,furi_hal_rtc_set_alarm_callback,void,"FuriHalRtcAlarmCallback, void*" Function,+,furi_hal_rtc_set_boot_mode,void,FuriHalRtcBootMode Function,+,furi_hal_rtc_set_datetime,void,DateTime* Function,+,furi_hal_rtc_set_fault_data,void,uint32_t @@ -3115,6 +3119,7 @@ Variable,+,sequence_display_backlight_off,const NotificationSequence, Variable,+,sequence_display_backlight_off_delay_1000,const NotificationSequence, Variable,+,sequence_display_backlight_on,const NotificationSequence, Variable,+,sequence_double_vibro,const NotificationSequence, +Variable,+,sequence_empty,const NotificationSequence, Variable,+,sequence_error,const NotificationSequence, Variable,+,sequence_lcd_contrast_update,const NotificationSequence, Variable,+,sequence_not_charging,const NotificationSequence, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 2bbbe53b76..ee81f76a97 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,78.0,, +Version,+,78.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1566,6 +1566,7 @@ Function,+,furi_hal_rfid_tim_read_pause,void, Function,+,furi_hal_rfid_tim_read_start,void,"float, float" Function,+,furi_hal_rfid_tim_read_stop,void, Function,-,furi_hal_rtc_deinit_early,void, +Function,-,furi_hal_rtc_get_alarm,_Bool,DateTime* Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, Function,+,furi_hal_rtc_get_datetime,void,DateTime* Function,+,furi_hal_rtc_get_fault_data,uint32_t, @@ -1583,8 +1584,11 @@ Function,+,furi_hal_rtc_get_timestamp,uint32_t, Function,-,furi_hal_rtc_init,void, Function,-,furi_hal_rtc_init_early,void, Function,+,furi_hal_rtc_is_flag_set,_Bool,FuriHalRtcFlag +Function,-,furi_hal_rtc_prepare_for_shutdown,void, Function,+,furi_hal_rtc_reset_flag,void,FuriHalRtcFlag Function,+,furi_hal_rtc_reset_registers,void, +Function,-,furi_hal_rtc_set_alarm,void,"const DateTime*, _Bool" +Function,-,furi_hal_rtc_set_alarm_callback,void,"FuriHalRtcAlarmCallback, void*" Function,+,furi_hal_rtc_set_boot_mode,void,FuriHalRtcBootMode Function,+,furi_hal_rtc_set_datetime,void,DateTime* Function,+,furi_hal_rtc_set_fault_data,void,uint32_t @@ -3974,6 +3978,7 @@ Variable,+,sequence_display_backlight_off,const NotificationSequence, Variable,+,sequence_display_backlight_off_delay_1000,const NotificationSequence, Variable,+,sequence_display_backlight_on,const NotificationSequence, Variable,+,sequence_double_vibro,const NotificationSequence, +Variable,+,sequence_empty,const NotificationSequence, Variable,+,sequence_error,const NotificationSequence, Variable,+,sequence_lcd_contrast_update,const NotificationSequence, Variable,+,sequence_not_charging,const NotificationSequence, diff --git a/targets/f7/furi_hal/furi_hal_interrupt.c b/targets/f7/furi_hal/furi_hal_interrupt.c index cf10c8d33d..27872570e8 100644 --- a/targets/f7/furi_hal/furi_hal_interrupt.c +++ b/targets/f7/furi_hal/furi_hal_interrupt.c @@ -68,6 +68,9 @@ const IRQn_Type furi_hal_interrupt_irqn[FuriHalInterruptIdMax] = { // COMP [FuriHalInterruptIdCOMP] = COMP_IRQn, + // RTC + [FuriHalInterruptIdRtcAlarm] = RTC_Alarm_IRQn, + // HSEM [FuriHalInterruptIdHsem] = HSEM_IRQn, @@ -256,6 +259,10 @@ void DMA2_Channel7_IRQHandler(void) { furi_hal_interrupt_call(FuriHalInterruptIdDma2Ch7); } +void RTC_Alarm_IRQHandler(void) { + furi_hal_interrupt_call(FuriHalInterruptIdRtcAlarm); +} + void HSEM_IRQHandler(void) { furi_hal_interrupt_call(FuriHalInterruptIdHsem); } diff --git a/targets/f7/furi_hal/furi_hal_interrupt.h b/targets/f7/furi_hal/furi_hal_interrupt.h index 2326d3c0ac..5ea830cede 100644 --- a/targets/f7/furi_hal/furi_hal_interrupt.h +++ b/targets/f7/furi_hal/furi_hal_interrupt.h @@ -42,6 +42,9 @@ typedef enum { // Comp FuriHalInterruptIdCOMP, + // RTC + FuriHalInterruptIdRtcAlarm, + // HSEM FuriHalInterruptIdHsem, diff --git a/targets/f7/furi_hal/furi_hal_power.c b/targets/f7/furi_hal/furi_hal_power.c index 37c6a8b1bb..fe5c0cf173 100644 --- a/targets/f7/furi_hal/furi_hal_power.c +++ b/targets/f7/furi_hal/furi_hal_power.c @@ -326,6 +326,7 @@ void furi_hal_power_shutdown(void) { void furi_hal_power_off(void) { // Crutch: shutting down with ext 3V3 off is causing LSE to stop + furi_hal_rtc_prepare_for_shutdown(); furi_hal_power_enable_external_3_3v(); furi_hal_vibro_on(true); furi_delay_us(50000); diff --git a/targets/f7/furi_hal/furi_hal_rtc.c b/targets/f7/furi_hal/furi_hal_rtc.c index d5cda74767..ab592fb787 100644 --- a/targets/f7/furi_hal/furi_hal_rtc.c +++ b/targets/f7/furi_hal/furi_hal_rtc.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -42,6 +43,13 @@ typedef struct { _Static_assert(sizeof(SystemReg) == 4, "SystemReg size mismatch"); +typedef struct { + FuriHalRtcAlarmCallback alarm_callback; + void* alarm_callback_context; +} FuriHalRtc; + +static FuriHalRtc furi_hal_rtc = {}; + static const FuriHalSerialId furi_hal_rtc_log_devices[] = { [FuriHalRtcLogDeviceUsart] = FuriHalSerialIdUsart, [FuriHalRtcLogDeviceLpuart] = FuriHalSerialIdLpuart, @@ -60,6 +68,17 @@ static const uint32_t furi_hal_rtc_log_baud_rates[] = { [FuriHalRtcLogBaudRate1843200] = 1843200, }; +static void furi_hal_rtc_enter_init_mode(void) { + LL_RTC_EnableInitMode(RTC); + while(LL_RTC_IsActiveFlag_INIT(RTC) != 1) + ; +} + +static void furi_hal_rtc_exit_init_mode(void) { + LL_RTC_DisableInitMode(RTC); + furi_hal_rtc_sync_shadow(); +} + static void furi_hal_rtc_reset(void) { LL_RCC_ForceBackupDomainReset(); LL_RCC_ReleaseBackupDomainReset(); @@ -127,6 +146,36 @@ static void furi_hal_rtc_recover(void) { } } +static void furi_hal_rtc_alarm_handler(void* context) { + UNUSED(context); + + if(LL_RTC_IsActiveFlag_ALRA(RTC) != 0) { + /* Clear the Alarm interrupt pending bit */ + LL_RTC_ClearFlag_ALRA(RTC); + + /* Alarm callback */ + furi_check(furi_hal_rtc.alarm_callback); + furi_hal_rtc.alarm_callback(furi_hal_rtc.alarm_callback_context); + } + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_17); +} + +static void furi_hal_rtc_set_alarm_out(bool enable) { + FURI_CRITICAL_ENTER(); + LL_RTC_DisableWriteProtection(RTC); + if(enable) { + LL_RTC_SetAlarmOutEvent(RTC, LL_RTC_ALARMOUT_ALMA); + LL_RTC_SetOutputPolarity(RTC, LL_RTC_OUTPUTPOLARITY_PIN_LOW); + LL_RTC_SetAlarmOutputType(RTC, LL_RTC_ALARM_OUTPUTTYPE_OPENDRAIN); + } else { + LL_RTC_SetAlarmOutEvent(RTC, LL_RTC_ALARMOUT_DISABLE); + LL_RTC_SetOutputPolarity(RTC, LL_RTC_OUTPUTPOLARITY_PIN_LOW); + LL_RTC_SetAlarmOutputType(RTC, LL_RTC_ALARM_OUTPUTTYPE_OPENDRAIN); + } + LL_RTC_EnableWriteProtection(RTC); + FURI_CRITICAL_EXIT(); +} + void furi_hal_rtc_init_early(void) { // Enable RTCAPB clock LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_RTCAPB); @@ -167,6 +216,11 @@ void furi_hal_rtc_init(void) { furi_hal_rtc_log_baud_rates[furi_hal_rtc_get_log_baud_rate()]); FURI_LOG_I(TAG, "Init OK"); + furi_hal_rtc_set_alarm_out(false); +} + +void furi_hal_rtc_prepare_for_shutdown(void) { + furi_hal_rtc_set_alarm_out(true); } void furi_hal_rtc_sync_shadow(void) { @@ -347,9 +401,7 @@ void furi_hal_rtc_set_datetime(DateTime* datetime) { LL_RTC_DisableWriteProtection(RTC); /* Enter Initialization mode and wait for INIT flag to be set */ - LL_RTC_EnableInitMode(RTC); - while(!LL_RTC_IsActiveFlag_INIT(RTC)) { - } + furi_hal_rtc_enter_init_mode(); /* Set time */ LL_RTC_TIME_Config( @@ -368,9 +420,7 @@ void furi_hal_rtc_set_datetime(DateTime* datetime) { __LL_RTC_CONVERT_BIN2BCD(datetime->year - 2000)); /* Exit Initialization mode */ - LL_RTC_DisableInitMode(RTC); - - furi_hal_rtc_sync_shadow(); + furi_hal_rtc_exit_init_mode(); /* Enable write protection */ LL_RTC_EnableWriteProtection(RTC); @@ -395,6 +445,82 @@ void furi_hal_rtc_get_datetime(DateTime* datetime) { datetime->weekday = __LL_RTC_CONVERT_BCD2BIN((date >> 24) & 0xFF); } +void furi_hal_rtc_set_alarm(const DateTime* datetime, bool enabled) { + furi_check(!FURI_IS_IRQ_MODE()); + + FURI_CRITICAL_ENTER(); + LL_RTC_DisableWriteProtection(RTC); + + if(datetime) { + LL_RTC_ALMA_ConfigTime( + RTC, + LL_RTC_ALMA_TIME_FORMAT_AM, + __LL_RTC_CONVERT_BIN2BCD(datetime->hour), + __LL_RTC_CONVERT_BIN2BCD(datetime->minute), + __LL_RTC_CONVERT_BIN2BCD(datetime->second)); + LL_RTC_ALMA_SetMask(RTC, LL_RTC_ALMA_MASK_DATEWEEKDAY); + } + + if(enabled) { + LL_RTC_ClearFlag_ALRA(RTC); + LL_RTC_ALMA_Enable(RTC); + } else { + LL_RTC_ALMA_Disable(RTC); + LL_RTC_ClearFlag_ALRA(RTC); + } + + LL_RTC_EnableWriteProtection(RTC); + FURI_CRITICAL_EXIT(); +} + +bool furi_hal_rtc_get_alarm(DateTime* datetime) { + furi_check(datetime); + + memset(datetime, 0, sizeof(DateTime)); + + datetime->hour = __LL_RTC_CONVERT_BCD2BIN(LL_RTC_ALMA_GetHour(RTC)); + datetime->minute = __LL_RTC_CONVERT_BCD2BIN(LL_RTC_ALMA_GetMinute(RTC)); + datetime->second = __LL_RTC_CONVERT_BCD2BIN(LL_RTC_ALMA_GetSecond(RTC)); + + return READ_BIT(RTC->CR, RTC_CR_ALRAE); +} + +void furi_hal_rtc_set_alarm_callback(FuriHalRtcAlarmCallback callback, void* context) { + FURI_CRITICAL_ENTER(); + LL_RTC_DisableWriteProtection(RTC); + if(callback) { + furi_check(!furi_hal_rtc.alarm_callback); + // Set our callbacks + furi_hal_rtc.alarm_callback = callback; + furi_hal_rtc.alarm_callback_context = context; + // Enable RTC ISR + furi_hal_interrupt_set_isr(FuriHalInterruptIdRtcAlarm, furi_hal_rtc_alarm_handler, NULL); + // Hello EXTI my old friend + // Chain: RTC->LINE-17->EXTI->NVIC->FuriHalInterruptIdRtcAlarm + LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINE_17); + LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_17); + // Enable alarm interrupt + LL_RTC_EnableIT_ALRA(RTC); + // Force trigger + furi_hal_rtc_alarm_handler(NULL); + } else { + furi_check(furi_hal_rtc.alarm_callback); + // Cleanup EXTI flags and config + LL_EXTI_DisableIT_0_31(LL_EXTI_LINE_17); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_17); + LL_EXTI_DisableRisingTrig_0_31(LL_EXTI_LINE_17); + // Cleanup NVIC flags and config + furi_hal_interrupt_set_isr(FuriHalInterruptIdRtcAlarm, NULL, NULL); + // Disable alarm interrupt + LL_RTC_DisableIT_ALRA(RTC); + + furi_hal_rtc.alarm_callback = NULL; + furi_hal_rtc.alarm_callback_context = NULL; + } + LL_RTC_EnableWriteProtection(RTC); + FURI_CRITICAL_EXIT(); +} + void furi_hal_rtc_set_fault_data(uint32_t value) { furi_hal_rtc_set_register(FuriHalRtcRegisterFaultData, value); } diff --git a/targets/f7/furi_hal/furi_hal_rtc.h b/targets/f7/furi_hal/furi_hal_rtc.h index c5eab12ec5..9c48fac0b5 100644 --- a/targets/f7/furi_hal/furi_hal_rtc.h +++ b/targets/f7/furi_hal/furi_hal_rtc.h @@ -98,6 +98,14 @@ void furi_hal_rtc_deinit_early(void); /** Initialize RTC subsystem */ void furi_hal_rtc_init(void); +/** Prepare system for shutdown + * + * This function must be called before system sent to transport mode(power off). + * FlipperZero implementation configures and enables ALARM output on pin PC13 + * (Back button). This allows the system to wake-up charger from transport mode. + */ +void furi_hal_rtc_prepare_for_shutdown(void); + /** Force sync shadow registers */ void furi_hal_rtc_sync_shadow(void); @@ -247,6 +255,38 @@ void furi_hal_rtc_set_datetime(DateTime* datetime); */ void furi_hal_rtc_get_datetime(DateTime* datetime); +/** Set alarm + * + * @param[in] datetime The date time to set or NULL if time change is not needed + * @param[in] enabled Indicates if alarm must be enabled or disabled + */ +void furi_hal_rtc_set_alarm(const DateTime* datetime, bool enabled); + +/** Get alarm + * + * @param datetime Pointer to DateTime object + * + * @return true if alarm was set, false otherwise + */ +bool furi_hal_rtc_get_alarm(DateTime* datetime); + +/** Furi HAL RTC alarm callback signature */ +typedef void (*FuriHalRtcAlarmCallback)(void* context); + +/** Set alarm callback + * + * Use it to subscribe to alarm trigger event. Setting alarm callback is + * independent from setting alarm. + * + * @warning Normally this callback will be delivered from the ISR, however we may + * deliver it while this function is called. This happens when + * the alarm has already triggered, but there was no ISR set. + * + * @param[in] callback The callback + * @param context The context + */ +void furi_hal_rtc_set_alarm_callback(FuriHalRtcAlarmCallback callback, void* context); + /** Set RTC Fault Data * * @param[in] value The value diff --git a/targets/furi_hal_include/furi_hal.h b/targets/furi_hal_include/furi_hal.h index cf483553f1..284bd558a3 100644 --- a/targets/furi_hal_include/furi_hal.h +++ b/targets/furi_hal_include/furi_hal.h @@ -46,13 +46,24 @@ struct STOP_EXTERNING_ME {}; extern "C" { #endif -/** Early FuriHal init, only essential subsystems */ +/** Early FuriHal init + * + * Init essential subsystems used in pre-DFU stage. + * This state can be undone with `furi_hal_deinit_early`. + * + */ void furi_hal_init_early(void); -/** Early FuriHal deinit */ +/** Early FuriHal deinit + * + * Undo `furi_hal_init_early`, prepare system for switch to another firmware/bootloader. + */ void furi_hal_deinit_early(void); -/** Init FuriHal */ +/** Init FuriHal + * + * Initialize the rest of the HAL, must be used after `furi_hal_init_early`. + */ void furi_hal_init(void); /** Jump to the void*