From 857e18e5cb2b764096bd847c42c6daa1656dd8b2 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Thu, 13 Aug 2026 22:37:53 -0400 Subject: [PATCH] Initial awareness alarm implementation --- src/c/main.c | 94 +++++++++++++++++++++++++++++------------------ src/c/main.h | 11 ++++++ src/c/scheduler.c | 8 ++-- src/c/scheduler.h | 9 ++++- 4 files changed, 81 insertions(+), 41 deletions(-) diff --git a/src/c/main.c b/src/c/main.c index 1c9049c..55c7251 100644 --- a/src/c/main.c +++ b/src/c/main.c @@ -12,7 +12,6 @@ static Window *s_drop_window; static Layer *s_top_bar_layer; static Layer *s_bottom_bar_layer; static TextLayer *s_counter_txt_layer; -static ScrollLayer *s_status_scroll_layer; static TextLayer *s_status_txt_layer; //// setter static BitmapLayer *s_clock_bmp_layer; @@ -54,6 +53,7 @@ static GBitmap *s_check_bmp; static GColor s_setter_highlight_color; static uint8_t s_highlight_pos = 0; //// alarm +static bool s_alarming_for_awareness; static uint8_t s_solution_indices[3] = {8, 8, 8}; static uint8_t s_selected_indices[3] = {9, 9, 9}; static uint8_t s_stopwatch_time[2] = {0, 0}; @@ -65,8 +65,6 @@ static const uint8_t s_edge_bar_height = ((PBL_DISPLAY_HEIGHT / 7) * 2); static const GRect s_top_bar_grect = GRect(0, 0, PBL_DISPLAY_WIDTH, s_edge_bar_height); static const GRect s_bottom_bar_grect = GRect(0, PBL_DISPLAY_HEIGHT - s_edge_bar_height, PBL_DISPLAY_WIDTH, s_edge_bar_height); static const GColor s_primary_color = GColorJaegerGreen; -#define STORAGE_KEY_ALARM_HOUR 0 -#define STORAGE_KEY_ALARM_MINUTE 1 //// alarm static const uint8_t s_drop_height = 22; static const GRect s_drop_function_grect = GRect(PBL_IF_ROUND_ELSE(55, 45), s_bottom_bar_grect.origin.y + ((s_bottom_bar_grect.size.h / 2) - PBL_IF_ROUND_ELSE(s_drop_height - 1, s_drop_height / 2)), 50, s_drop_height); @@ -118,24 +116,22 @@ static void status_2_click_config_provider(void *ctx) { window_single_click_subscribe(BUTTON_ID_SELECT, status_2_select_click_handler); } -static void status_window_load(uint8_t reason) { +static void status_window_load(StatusWindowPurpose purpose) { Layer *window_layer = window_get_root_layer(s_status_window); - s_status_scroll_layer = scroll_layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT)); - - switch (reason) { - case 0: + switch (purpose) { + case STATUS_CONFIRMATION: tick_timer_service_unsubscribe(); s_status_txt_layer = text_layer_create(GRect(0, 8, PBL_DISPLAY_WIDTH, 500)); text_layer_set_font(s_status_txt_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24)); - text_layer_set_text(s_status_txt_layer, "Alarm disarmed and re-set successfully.\n\nLast seven times:\n1. 00:00\n2. 00:00\n3. 00:00\n4. 00:00\n5. 00:00\n6. 00:00\n7. 00:00"); - window_set_click_config_provider_with_context(s_status_window, status_0_click_config_provider, s_status_scroll_layer); + text_layer_set_text(s_status_txt_layer, "Alarm disarmed and re-set successfully."); + window_set_click_config_provider(s_status_window, status_0_click_config_provider); break; - case 1: + case STATUS_SET_FAILURE: tick_timer_service_unsubscribe(); s_status_txt_layer = text_layer_create(GRect(0, PBL_DISPLAY_HEIGHT / 2, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT)); text_layer_set_font(s_status_txt_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28)); - text_layer_set_text(s_status_txt_layer, "Failed to set primary alarm.\nPress any button to return to alarm setter."); + text_layer_set_text(s_status_txt_layer, "Failed to set an alarm.\nPress any button to return to alarm setter."); window_set_click_config_provider(s_status_window, status_1_click_config_provider); break; case 2: @@ -147,38 +143,48 @@ static void status_window_load(uint8_t reason) { text_layer_set_background_color(s_status_txt_layer, GColorClear); text_layer_set_text_alignment(s_status_txt_layer, GTextAlignmentCenter); - scroll_layer_add_child(s_status_scroll_layer, text_layer_get_layer(s_status_txt_layer)); - layer_add_child(window_layer, scroll_layer_get_layer(s_status_scroll_layer)); - GSize max_size = text_layer_get_content_size(s_status_txt_layer); - max_size.h += 10; - scroll_layer_set_content_size(s_status_scroll_layer, max_size); + layer_add_child(window_layer, text_layer_get_layer(s_status_txt_layer)); } static void status_window_unload() { text_layer_destroy(s_status_txt_layer); - scroll_layer_destroy(s_status_scroll_layer); } -// reasons: 0 = confirmation, 1 = failure to set alarm, 2 = user sucks at math -static void push_status_window(uint8_t reason) { +static void push_status_window(StatusWindowPurpose purpose) { s_status_window = window_create(); window_set_window_handlers( s_status_window, (WindowHandlers){.unload = status_window_unload}); - status_window_load(reason); + status_window_load(purpose); window_stack_push(s_status_window, true); } ////////////////////// MISC ////////////////////// -static bool schedule_alarm(bool is_fallback) { +static bool schedule_alarm(WakeupReason schedule_reason) { time_t target; WakeupId status; - if (is_fallback) { + switch (schedule_reason) { + case RECOVERY_ALARM: target = time(NULL) + 15; - status = wakeup_schedule_simple_robust(target, 15, true); - } else { + // no need to cancel alarms, as RECOVERY_ALARM is short-lived and is expired by the time another is being scheduled + status = wakeup_schedule_simple_robust(target, 15, schedule_reason); + if (s_alarming_for_awareness) { + persist_write_bool(STORAGE_KEY_ALARMING_FOR_AWARENESS, false); + s_alarming_for_awareness = false; + } + break; + case AWARENESS_ALARM: + target = time(NULL) + 9 * SECONDS_PER_MINUTE; + wakeup_cancel_all(); + status = wakeup_schedule_simple_robust(target, SECONDS_PER_MINUTE, schedule_reason); + if (!s_alarming_for_awareness) { + persist_write_bool(STORAGE_KEY_ALARMING_FOR_AWARENESS, true); + s_alarming_for_awareness = true; + } + break; + case MAIN_ALARM: { time_t now = time(NULL); struct tm target_tm = *localtime(&now); target_tm.tm_hour = the_time.HourWIP; @@ -188,8 +194,13 @@ static bool schedule_alarm(bool is_fallback) { if (target <= now) { target += SECONDS_PER_DAY; } - wakeup_cancel_all(); // only cancel for main alarm (fallback alarm has short lifetime) - status = wakeup_schedule_simple_robust(target, SECONDS_PER_MINUTE, is_fallback); + wakeup_cancel_all(); + status = wakeup_schedule_simple_robust(target, SECONDS_PER_MINUTE, schedule_reason); + if (s_alarming_for_awareness) { + persist_write_bool(STORAGE_KEY_ALARMING_FOR_AWARENESS, false); + s_alarming_for_awareness = false; + } + } } if (status < 0) { push_status_window(1); @@ -234,7 +245,9 @@ static void alarm_seconds_handler(struct tm *tick_time, TimeUnits units_changed) static char s_stopwatch_buffer[9]; snprintf(s_stopwatch_buffer, sizeof(s_stopwatch_buffer), "%02u:%02u", s_stopwatch_time[0], s_stopwatch_time[1]); text_layer_set_text(s_counter_txt_layer, s_stopwatch_buffer); - vibes_enqueue_custom_pattern(s_vibe_pattern); + if (!s_alarming_for_awareness) { + vibes_enqueue_custom_pattern(s_vibe_pattern); + } } static void graph_layer_update_proc(Layer *layer, GContext *ctx) { @@ -320,9 +333,9 @@ static void drop_menu_window_unload(Window *window) { s_drop_menu_layer = NULL; } -static void live_wakeup_handler_for_alarm(WakeupId id, int32_t reason) { - if (reason == 1) { // if fallback alarm, re-schedule it - schedule_alarm(true); // output is not captured, as it is not actionable +static void live_wakeup_handler_for_alarm(WakeupId id, int32_t wakeup_reason) { + if (wakeup_reason == RECOVERY_ALARM) { // if recovery alarm, re-schedule it + schedule_alarm(RECOVERY_ALARM); // output is not captured, as it is not actionable } } @@ -563,7 +576,7 @@ static void alarm_window_load() { .num_segments = ARRAY_LENGTH(s_vibe_segments), }; - schedule_alarm(true); // output is not captured, as it is not actionable + schedule_alarm(RECOVERY_ALARM); // output is not captured, as it is not actionable tick_timer_service_subscribe(SECOND_UNIT, alarm_seconds_handler); } @@ -636,9 +649,16 @@ static void alarm_select_hold_handler(void *recognizer, void *ctx) { } } - bool success = schedule_alarm(false); - if (success) { - push_status_window(0); + if (s_alarming_for_awareness) { + // set main alarm if the awareness alarm was solved + if (schedule_alarm(MAIN_ALARM)) { + push_status_window(0); + } + } else { + // otherwise, set awareness alarm + if (schedule_alarm(AWARENESS_ALARM)) { + window_stack_pop_all(true); + } } } @@ -759,7 +779,7 @@ static void setter_down_click_handler(void *recognizer, void *ctx) { static void setter_select_click_handler(void *recognizer, void *ctx) { if (s_highlight_pos == 2) { - bool success = schedule_alarm(false); + bool success = schedule_alarm(MAIN_ALARM); if (!success) { return; // early return to avoid indicating alarm was set when it was not } @@ -798,6 +818,7 @@ static void setter_click_config_provider(void *ctx) { } static void live_wakeup_handler_for_setter(WakeupId id, int32_t reason) { + s_alarming_for_awareness = persist_read_bool(STORAGE_KEY_ALARMING_FOR_AWARENESS); s_highlight_pos = 0; window_stack_pop_all(false); push_alarm_window(); @@ -886,6 +907,7 @@ static void init() { switch (launch_reason()) { case APP_LAUNCH_PHONE: // DEBUG, REMOVE PRIOR TO RELEASE! case APP_LAUNCH_WAKEUP: + s_alarming_for_awareness = persist_read_bool(STORAGE_KEY_ALARMING_FOR_AWARENESS); push_alarm_window(); break; default: diff --git a/src/c/main.h b/src/c/main.h index f0bff74..305b9be 100644 --- a/src/c/main.h +++ b/src/c/main.h @@ -1,3 +1,14 @@ #pragma once +#define STORAGE_KEY_ALARM_HOUR 0 +#define STORAGE_KEY_ALARM_MINUTE 1 +#define STORAGE_KEY_ALARMING_FOR_AWARENESS 2 + +typedef enum +{ + STATUS_CONFIRMATION = 0, + STATUS_SET_FAILURE = 1, + STATUS_MATH_FAILURE = 2 +} StatusWindowPurpose; + static void push_setter_window(); diff --git a/src/c/scheduler.c b/src/c/scheduler.c index 075e625..e29754d 100644 --- a/src/c/scheduler.c +++ b/src/c/scheduler.c @@ -17,24 +17,24 @@ static void glance_reload_callback(AppGlanceReloadSession *session, size_t limit // Mostly stolen from https://github.com/SeaPea/GentleWake; // A slightly more robust wakeup scheduler that retries on certain errors and // can retry for a different wakeup time offset by retry_diff up to retry_max times -WakeupId wakeup_schedule_simple_robust(time_t wakeup_time, int8_t retry_diff, bool is_fallback) { +WakeupId wakeup_schedule_simple_robust(time_t wakeup_time, int8_t retry_diff, WakeupReason wakeup_reason) { WakeupId result = 0; uint8_t try_count = 0; - result = wakeup_schedule(wakeup_time, is_fallback, true); + result = wakeup_schedule(wakeup_time, wakeup_reason, true); if (result < 0) { // If result is negative, something went wrong, so make sure all wakeups for this app are cancelled and try again wakeup_cancel_all(); - result = wakeup_schedule(wakeup_time, is_fallback, true); + result = wakeup_schedule(wakeup_time, wakeup_reason, true); if (result == E_RANGE) { // E_RANGE means some other app has the same wakeup time +/- 1 minute, so try to set // the wakeup for a time up to (retry_diff * 8) minutes before/after. while (result == E_RANGE && try_count++ < 8) { wakeup_time += retry_diff; - result = wakeup_schedule(wakeup_time, is_fallback, true); + result = wakeup_schedule(wakeup_time, wakeup_reason, true); } } } diff --git a/src/c/scheduler.h b/src/c/scheduler.h index c95891c..c393640 100644 --- a/src/c/scheduler.h +++ b/src/c/scheduler.h @@ -1,6 +1,13 @@ #pragma once #include +typedef enum +{ + MAIN_ALARM = 0, + AWARENESS_ALARM = 1, + RECOVERY_ALARM = 2 +} WakeupReason; + typedef struct TheTime { uint8_t HourWIP; uint8_t MinuteWIP; @@ -10,4 +17,4 @@ typedef struct TheTime { struct TheTime the_time; -WakeupId wakeup_schedule_simple_robust(time_t wakeup_time, int8_t retry_diff, bool is_fallback); +WakeupId wakeup_schedule_simple_robust(time_t wakeup_time, int8_t retry_diff, WakeupReason wakeup_reason);