目覚め時計 (#3906)

* FuriHal: add RTC alarm support

* FuriHal: RTC alarm API. Alarm settings app. Alarm app.

* FuriHal: remove unnecessery init mode enters in RTC

* Update targets/f7/furi_hal/furi_hal_rtc.h

Co-authored-by: Silent <CookiePLMonster@users.noreply.github.com>

* Update targets/f7/furi_hal/furi_hal_rtc.c

Co-authored-by: Silent <CookiePLMonster@users.noreply.github.com>

* Update targets/f7/furi_hal/furi_hal_rtc.h

Co-authored-by: Silent <CookiePLMonster@users.noreply.github.com>

* FuriHal: add seconds in rtc alarm getter

* Alarm & Clock: redesign and cleanup setting and alarm apps, cleanup API

* Spelling and time separator in alarm

* Api Symbols: hide rtc alarm related methods

* Clock alarm: new thread cleanup routine, hour/minute separator in alarm

* Clock: move clock_settings_start into clock_settings fam

* Seettings: update clock and alarm UI according to figma

* Format icons

---------

Co-authored-by: Silent <CookiePLMonster@users.noreply.github.com>
This commit is contained in:
あく 2024-10-31 20:42:03 +09:00 committed by GitHub
parent 4b8a1a4b11
commit 561b4e947a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 1068 additions and 14 deletions

View File

@ -593,3 +593,7 @@ const NotificationSequence sequence_lcd_contrast_update = {
&message_lcd_contrast_update,
NULL,
};
const NotificationSequence sequence_empty = {
NULL,
};

View File

@ -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

View File

@ -5,6 +5,7 @@ App(
provides=[
"passport",
"system_settings",
"clock_settings",
"about",
],
)

View File

@ -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,
)

View File

@ -0,0 +1,71 @@
#include "clock_settings.h"
#include <furi.h>
#include <furi_hal.h>
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;
}

View File

@ -0,0 +1,31 @@
#pragma once
#include "scenes/clock_settings_scene.h"
#include <furi_hal_clock.h>
#include <furi_hal_pwm.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/submenu.h>
#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;

View File

@ -0,0 +1,177 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view_port.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <assets_icons.h>
#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
}

View File

@ -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,
};

View File

@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// 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

View File

@ -0,0 +1 @@
ADD_SCENE(clock_settings, start, Start)

View File

@ -0,0 +1,32 @@
#include "../clock_settings.h"
#include <furi_hal.h>
#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);
}

View File

@ -0,0 +1,438 @@
#include "clock_settings_module.h"
#include <gui/elements.h>
#include <assets_icons.h>
#include <locale/locale.h>
#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;
}

View File

@ -0,0 +1,24 @@
#pragma once
#include <furi_hal.h>
#include <gui/view.h>
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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

View File

@ -0,0 +1 @@
2

View File

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

View File

@ -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,

1 entry status name type params
2 Version + 78.0 78.1
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/bt/bt_service/bt_keys_storage.h
5 Header + applications/services/cli/cli.h
1377 Function + furi_hal_resources_pin_by_name const GpioPinRecord* const char*
1378 Function + furi_hal_resources_pin_by_number const GpioPinRecord* uint8_t
1379 Function - furi_hal_rtc_deinit_early void
1380 Function - furi_hal_rtc_get_alarm _Bool DateTime*
1381 Function + furi_hal_rtc_get_boot_mode FuriHalRtcBootMode
1382 Function + furi_hal_rtc_get_datetime void DateTime*
1383 Function + furi_hal_rtc_get_fault_data uint32_t
1395 Function - furi_hal_rtc_init void
1396 Function - furi_hal_rtc_init_early void
1397 Function + furi_hal_rtc_is_flag_set _Bool FuriHalRtcFlag
1398 Function - furi_hal_rtc_prepare_for_shutdown void
1399 Function + furi_hal_rtc_reset_flag void FuriHalRtcFlag
1400 Function + furi_hal_rtc_reset_registers void
1401 Function - furi_hal_rtc_set_alarm void const DateTime*, _Bool
1402 Function - furi_hal_rtc_set_alarm_callback void FuriHalRtcAlarmCallback, void*
1403 Function + furi_hal_rtc_set_boot_mode void FuriHalRtcBootMode
1404 Function + furi_hal_rtc_set_datetime void DateTime*
1405 Function + furi_hal_rtc_set_fault_data void uint32_t
3119 Variable + sequence_display_backlight_off_delay_1000 const NotificationSequence
3120 Variable + sequence_display_backlight_on const NotificationSequence
3121 Variable + sequence_double_vibro const NotificationSequence
3122 Variable + sequence_empty const NotificationSequence
3123 Variable + sequence_error const NotificationSequence
3124 Variable + sequence_lcd_contrast_update const NotificationSequence
3125 Variable + sequence_not_charging const NotificationSequence

View File

@ -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,

1 entry status name type params
2 Version + 78.0 78.1
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/bt/bt_service/bt_keys_storage.h
1566 Function + furi_hal_rfid_tim_read_start void float, float
1567 Function + furi_hal_rfid_tim_read_stop void
1568 Function - furi_hal_rtc_deinit_early void
1569 Function - furi_hal_rtc_get_alarm _Bool DateTime*
1570 Function + furi_hal_rtc_get_boot_mode FuriHalRtcBootMode
1571 Function + furi_hal_rtc_get_datetime void DateTime*
1572 Function + furi_hal_rtc_get_fault_data uint32_t
1584 Function - furi_hal_rtc_init void
1585 Function - furi_hal_rtc_init_early void
1586 Function + furi_hal_rtc_is_flag_set _Bool FuriHalRtcFlag
1587 Function - furi_hal_rtc_prepare_for_shutdown void
1588 Function + furi_hal_rtc_reset_flag void FuriHalRtcFlag
1589 Function + furi_hal_rtc_reset_registers void
1590 Function - furi_hal_rtc_set_alarm void const DateTime*, _Bool
1591 Function - furi_hal_rtc_set_alarm_callback void FuriHalRtcAlarmCallback, void*
1592 Function + furi_hal_rtc_set_boot_mode void FuriHalRtcBootMode
1593 Function + furi_hal_rtc_set_datetime void DateTime*
1594 Function + furi_hal_rtc_set_fault_data void uint32_t
3978 Variable + sequence_display_backlight_off_delay_1000 const NotificationSequence
3979 Variable + sequence_display_backlight_on const NotificationSequence
3980 Variable + sequence_double_vibro const NotificationSequence
3981 Variable + sequence_empty const NotificationSequence
3982 Variable + sequence_error const NotificationSequence
3983 Variable + sequence_lcd_contrast_update const NotificationSequence
3984 Variable + sequence_not_charging const NotificationSequence

View File

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

View File

@ -42,6 +42,9 @@ typedef enum {
// Comp
FuriHalInterruptIdCOMP,
// RTC
FuriHalInterruptIdRtcAlarm,
// HSEM
FuriHalInterruptIdHsem,

View File

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

View File

@ -1,3 +1,4 @@
#include <furi_hal_interrupt.h>
#include <furi_hal_rtc.h>
#include <furi_hal_light.h>
#include <furi_hal_debug.h>
@ -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);
}

View File

@ -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

View File

@ -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*