From dc108418129fc19e7c22914fe1c65351ba0d30a0 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Tue, 18 Aug 2026 23:02:55 -0400 Subject: [PATCH] Make awareness alarm configurable and vastly more useful --- package.json | 5 +- src/c/main.c | 151 ++++++++++++++++++++++++++++----------------- src/c/main.h | 6 -- src/c/settings.h | 20 ++++++ src/pkjs/config.js | 59 ++++++++++++++---- 5 files changed, 164 insertions(+), 77 deletions(-) create mode 100644 src/c/settings.h diff --git a/package.json b/package.json index 57f924b..e8c5001 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,10 @@ "messageKeys": [ "PKJS_READY", "PKJS_PAVLOK_ENABLE", - "PKJS_PAVLOK_ZAP" + "PKJS_PAVLOK_ZAP", + "PKJS_AWARENESS_TIME_GAP", + "PKJS_AWARENESS_TIME_LIMIT", + "PKJS_AWARENESS_NUDGE_ENABLE" ], "resources": { "media": [ diff --git a/src/c/main.c b/src/c/main.c index db684b0..293a6c8 100644 --- a/src/c/main.c +++ b/src/c/main.c @@ -1,17 +1,21 @@ #include "main.h" #include "scheduler.h" +#include "settings.h" #include "triangles.h" #include #include #include +// settings +static AppSettings settings; + // layer-age static Window *s_window; static Window *s_status_window; static Window *s_drop_window; static Layer *s_top_bar_layer; static Layer *s_bottom_bar_layer; -static TextLayer *s_counter_txt_layer; +static TextLayer *s_countdown_txt_layer; static TextLayer *s_status_txt_layer; //// setter static BitmapLayer *s_clock_bmp_layer; @@ -56,7 +60,8 @@ static uint8_t s_highlight_pos = 0; static WakeupReason s_alarm_reason; 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}; +static uint8_t s_awareness_countdown_int = 0; +static char s_awareness_countdown_buf[4]; static uint32_t s_vibe_segments[5]; static VibePattern s_vibe_pattern; @@ -85,7 +90,8 @@ static char *s_options_under[2]; ////////////////////// STATUS ////////////////////// static void click_handler_disable_tutorial(void *recognizer, void *ctx) { - persist_write_bool(STORAGE_KEY_DISABLE_TUTORIAL, true); + settings.TutorialEnabled = false; + persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings)); window_stack_pop_all(true); } @@ -133,7 +139,7 @@ static void status_window_load(StatusWindowPurpose purpose) { switch (purpose) { case STATUS_MAIN_TUTORIAL: // don't disable ticks to ensure vibrations remain till the tutorial is closed - text_layer_set_text(s_status_txt_layer, "Congrats! To ensure you're awake, I'll re-open without vibrating in 9 minutes. Complete the equation within 60 seconds to disarm me. Take too long and I'll vibrate and re-schedule another alarm.\n\nPress SELECT to exit or BACK to disable this tutorial in the future (and exit)."); + text_layer_set_text(s_status_txt_layer, "Correct! An awareness alarm is now being set. Solve it within the time limit to disarm Trigalarm.\n\nPress SELECT to exit or BACK to disable this tutorial in the future (and exit)."); window_set_click_config_provider(s_status_window, status_main_tutorial_click_config_provider); break; case STATUS_CONFIRMATION: { @@ -173,21 +179,27 @@ static void push_status_window(StatusWindowPurpose purpose) { ////////////////////// PKJS ////////////////////// +static void apply_uint8_from_tuple(DictionaryIterator *iter, uint32_t key, void *dest) { + Tuple *t = dict_find(iter, key); + if (!t) + return; + *(uint8_t *)dest = t->value->uint8; +} + static void inbox_received_handler(DictionaryIterator *iter, void *context) { Tuple *ready_tuple = dict_find(iter, MESSAGE_KEY_PKJS_READY); if (ready_tuple) { return; // TODO use ready signal better } - - Tuple *clay_tuple = dict_find(iter, MESSAGE_KEY_PKJS_PAVLOK_ENABLE); - if (clay_tuple) { - persist_write_bool(STORAGE_KEY_PAVLOK_ENABLED, clay_tuple->value->uint8); - return; - } + apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ENABLE, &settings.PavlokEnabled); + apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_AWARENESS_TIME_GAP, &settings.AwarenessTimeGapMinutes); + apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_AWARENESS_TIME_LIMIT, &settings.AwarenessTimeLimitSeconds); + apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_AWARENESS_NUDGE_ENABLE, &settings.AwarenessNudgeEnabled); + persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings)); } static void send_zap() { - if (persist_read_bool(STORAGE_KEY_PAVLOK_ENABLED) == false) { + if (!settings.PavlokEnabled) { return; } @@ -216,7 +228,7 @@ static bool schedule_alarm(WakeupReason schedule_reason) { status = wakeup_schedule_simple_robust(target, 15, schedule_reason); break; case WAKE_AWARENESS_ALARM: - target = time(NULL) + 9 * SECONDS_PER_MINUTE; + target = time(NULL) + settings.AwarenessTimeGapMinutes * SECONDS_PER_MINUTE; wakeup_cancel_all(); status = wakeup_schedule_simple_robust(target, SECONDS_PER_MINUTE, schedule_reason); break; @@ -239,7 +251,7 @@ static bool schedule_alarm(WakeupReason schedule_reason) { persist_write_int(STORAGE_KEY_ALARM_REASON, WAKE_MAIN_ALARM); return false; } - if (s_alarm_reason != schedule_reason) { + if (persist_read_int(STORAGE_KEY_ALARM_REASON) != schedule_reason) { persist_write_int(STORAGE_KEY_ALARM_REASON, schedule_reason); } return true; @@ -270,26 +282,20 @@ static void add_bar_layers_to_window(Layer *window_layer, bool small_top) { ////////////////////// ALARM ////////////////////// -static void alarm_seconds_handler(struct tm *tick_time, TimeUnits units_changed) { - // update stopwatch - if (s_stopwatch_time[1] == 59) { - s_stopwatch_time[1] = 0; - s_stopwatch_time[0]++; - send_zap(); - // fall back to main alarm and resume vibes - if (s_alarm_reason != WAKE_MAIN_ALARM) { - // this may be overwritten when the recovery alarm is set, - // but it will still allow vibes to occur since the alarm reason - // will not be awareness - s_alarm_reason = WAKE_MAIN_ALARM; +static void main_alarm_seconds_handler(struct tm *tick_time, TimeUnits units_changed) { + vibes_enqueue_custom_pattern(s_vibe_pattern); +} + +static void awareness_alarm_seconds_handler(struct tm *tick_time, TimeUnits units_changed) { + if (s_awareness_countdown_int > 0) { + s_awareness_countdown_int--; + snprintf(s_awareness_countdown_buf, sizeof(s_awareness_countdown_buf), "%03u", s_awareness_countdown_int); + text_layer_set_text(s_countdown_txt_layer, s_awareness_countdown_buf); + if (s_awareness_countdown_int == 0) { + send_zap(); // TODO implement repeat interval } } else { - s_stopwatch_time[1]++; - } - 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); - if (s_alarm_reason != WAKE_AWARENESS_ALARM) { + // TODO flash countdown txt layer every second vibes_enqueue_custom_pattern(s_vibe_pattern); } } @@ -600,14 +606,18 @@ static void alarm_window_load() { s_drop_window, (WindowHandlers){.load = drop_menu_window_load, .unload = drop_menu_window_unload}); - // stopwatch - s_counter_txt_layer = text_layer_create(GRect(0, (s_small_top_bar_height / 2) - PBL_IF_ROUND_ELSE(6, 10), PBL_DISPLAY_WIDTH, s_small_top_bar_height)); - text_layer_set_background_color(s_counter_txt_layer, GColorClear); - text_layer_set_text_color(s_counter_txt_layer, GColorWhite); - text_layer_set_text_alignment(s_counter_txt_layer, GTextAlignmentCenter); - text_layer_set_font(s_counter_txt_layer, s_font_lecoish_small); - text_layer_set_text(s_counter_txt_layer, "00:00"); - layer_add_child(window_layer, text_layer_get_layer(s_counter_txt_layer)); + // countdown + if (s_alarm_reason == WAKE_AWARENESS_ALARM) { + s_countdown_txt_layer = text_layer_create(GRect(0, (s_small_top_bar_height / 2) - PBL_IF_ROUND_ELSE(6, 10), PBL_DISPLAY_WIDTH, s_small_top_bar_height)); + text_layer_set_background_color(s_countdown_txt_layer, GColorClear); + text_layer_set_text_color(s_countdown_txt_layer, GColorWhite); + text_layer_set_text_alignment(s_countdown_txt_layer, GTextAlignmentCenter); + text_layer_set_font(s_countdown_txt_layer, s_font_lecoish_small); + s_awareness_countdown_int = settings.AwarenessTimeLimitSeconds; + snprintf(s_awareness_countdown_buf, sizeof(s_awareness_countdown_buf), "%03u", s_awareness_countdown_int); + text_layer_set_text(s_countdown_txt_layer, s_awareness_countdown_buf); + layer_add_child(window_layer, text_layer_get_layer(s_countdown_txt_layer)); + } // setup vibes s_vibe_segments[0] = (uint32_t)(s_triangle_layer->angle_A * 2); @@ -621,12 +631,18 @@ static void alarm_window_load() { }; schedule_alarm(WAKE_RECOVERY_ALARM); // output is not captured, as it is not actionable - tick_timer_service_subscribe(SECOND_UNIT, alarm_seconds_handler); + if (s_alarm_reason == WAKE_AWARENESS_ALARM) { + tick_timer_service_subscribe(SECOND_UNIT, awareness_alarm_seconds_handler); + } else { + tick_timer_service_subscribe(SECOND_UNIT, main_alarm_seconds_handler); + } } static void alarm_window_unload() { tick_timer_service_unsubscribe(); - text_layer_destroy(s_counter_txt_layer); + if (s_countdown_txt_layer != NULL) { + text_layer_destroy(s_countdown_txt_layer); + } window_destroy(s_drop_window); text_layer_destroy(s_side_a_txt_layer); text_layer_destroy(s_side_b_txt_layer); @@ -698,20 +714,19 @@ static void alarm_select_hold_handler(void *recognizer, void *ctx) { // no-longer-important recovery alarm wakeup_service_subscribe(NULL); - if (s_alarm_reason == WAKE_AWARENESS_ALARM) { - // set main alarm if the awareness alarm was solved - tick_timer_service_unsubscribe(); // alarm is fully disarmed - stop vibes+countdown for confirmation window + if (s_awareness_countdown_int > 0) { + // set main alarm if the awareness alarm was solved within time limit + tick_timer_service_unsubscribe(); // alarm is fully disarmed - prevent stimulus in confirmation window if (schedule_alarm(WAKE_MAIN_ALARM)) { push_status_window(STATUS_CONFIRMATION); } } else { // otherwise, set awareness alarm and exit if (schedule_alarm(WAKE_AWARENESS_ALARM)) { - if (persist_read_bool(STORAGE_KEY_DISABLE_TUTORIAL)) { - window_stack_pop_all(true); - } else { - // show tutorial if it has not yet been disabled + if (settings.TutorialEnabled && s_alarm_reason == WAKE_MAIN_ALARM) { push_status_window(STATUS_MAIN_TUTORIAL); + } else { + window_stack_pop_all(true); } } } @@ -759,7 +774,7 @@ static void setter_seconds_handler(struct tm *tick_time, TimeUnits units_changed static char s_countdown_buffer[12]; snprintf(s_countdown_buffer, sizeof(s_countdown_buffer), "%02u:%02u:%02u", hours, minutes, seconds); - text_layer_set_text(s_counter_txt_layer, s_countdown_buffer); + text_layer_set_text(s_countdown_txt_layer, s_countdown_buffer); } static void update_countdown_display() { @@ -909,12 +924,12 @@ static void setter_window_load() { layer_add_child(window_layer, bitmap_layer_get_layer(s_check_bmp_layer)); // countdown - s_counter_txt_layer = text_layer_create(GRect(PBL_IF_ROUND_ELSE(0, 43), (s_top_bar_grect.size.h / 2) + PBL_IF_ROUND_ELSE(10, -10), PBL_IF_ROUND_ELSE(s_top_bar_grect.size.w, s_top_bar_grect.size.w - 39), 32)); - text_layer_set_text_alignment(s_counter_txt_layer, GTextAlignmentCenter); - text_layer_set_text_color(s_counter_txt_layer, GColorWhite); - text_layer_set_background_color(s_counter_txt_layer, GColorClear); - text_layer_set_font(s_counter_txt_layer, s_font_lecoish_small); - layer_add_child(window_layer, text_layer_get_layer(s_counter_txt_layer)); + s_countdown_txt_layer = text_layer_create(GRect(PBL_IF_ROUND_ELSE(0, 43), (s_top_bar_grect.size.h / 2) + PBL_IF_ROUND_ELSE(10, -10), PBL_IF_ROUND_ELSE(s_top_bar_grect.size.w, s_top_bar_grect.size.w - 39), 32)); + text_layer_set_text_alignment(s_countdown_txt_layer, GTextAlignmentCenter); + text_layer_set_text_color(s_countdown_txt_layer, GColorWhite); + text_layer_set_background_color(s_countdown_txt_layer, GColorClear); + text_layer_set_font(s_countdown_txt_layer, s_font_lecoish_small); + layer_add_child(window_layer, text_layer_get_layer(s_countdown_txt_layer)); update_countdown_display(); // highlighter @@ -938,7 +953,7 @@ static void setter_window_unload() { tick_timer_service_unsubscribe(); text_layer_destroy(s_setter_txt_layer); layer_destroy(s_setter_highlight_layer); - text_layer_destroy(s_counter_txt_layer); + text_layer_destroy(s_countdown_txt_layer); bitmap_layer_destroy(s_check_bmp_layer); bitmap_layer_destroy(s_clock_bmp_layer); gbitmap_destroy(s_check_bmp); @@ -964,15 +979,35 @@ static void init() { the_time.HourSet = the_time.HourWIP; the_time.MinuteSet = the_time.MinuteWIP; - // initialize pkjs TODO maybe only do in alarm mode? + // initialize pkjs app_message_register_inbox_received(inbox_received_handler); app_message_open(64, 64); // TODO tighten sizes + // load settings from persist (or set defaults if not present) + if (persist_exists(STORAGE_KEY_SETTINGS)) { + persist_read_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings)); + } else { + settings.TutorialEnabled = true; + settings.PavlokEnabled = false; + settings.AwarenessTimeGapMinutes = 9; + settings.AwarenessTimeLimitSeconds = 30; + settings.AwarenessNudgeEnabled = true; + } + s_alarm_reason = persist_read_int(STORAGE_KEY_ALARM_REASON); switch (launch_reason()) { case APP_LAUNCH_WAKEUP: - if (s_alarm_reason == WAKE_RECOVERY_ALARM) { + switch (s_alarm_reason) { + case WAKE_RECOVERY_ALARM: send_zap(); + break; + case WAKE_AWARENESS_ALARM: + if (settings.AwarenessNudgeEnabled) { + vibes_short_pulse(); + } + break; + default: + break; } push_alarm_window(); break; diff --git a/src/c/main.h b/src/c/main.h index c9bb76b..97c186b 100644 --- a/src/c/main.h +++ b/src/c/main.h @@ -1,11 +1,5 @@ #pragma once -#define STORAGE_KEY_ALARM_REASON 10 // BUMP ON UPDATES TO PREVENT USERS GETTING STUCK W/O ALARM -#define STORAGE_KEY_ALARM_HOUR 0 -#define STORAGE_KEY_ALARM_MINUTE 1 -#define STORAGE_KEY_DISABLE_TUTORIAL 2 -#define STORAGE_KEY_PAVLOK_ENABLED 3 - typedef enum { STATUS_MAIN_TUTORIAL = 0, diff --git a/src/c/settings.h b/src/c/settings.h new file mode 100644 index 0000000..2bec52c --- /dev/null +++ b/src/c/settings.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +// BUMP ON UPDATES +#define STORAGE_KEY_SETTINGS 50 +#define STORAGE_KEY_ALARM_REASON 100 + +#define STORAGE_KEY_ALARM_HOUR 0 +#define STORAGE_KEY_ALARM_MINUTE 1 + +typedef struct AppSettings { + bool TutorialEnabled; + bool PavlokEnabled; + uint8_t AwarenessTimeGapMinutes; + uint8_t AwarenessTimeLimitSeconds; + uint8_t AwarenessNudgeEnabled; +} __attribute__((__packed__)) AppSettings; + +_Static_assert(sizeof(AppSettings) <= 256, "AppSettings exceeds Pebble 256-byte persist limit!"); diff --git a/src/pkjs/config.js b/src/pkjs/config.js index d8d93b6..2f8e7af 100644 --- a/src/pkjs/config.js +++ b/src/pkjs/config.js @@ -19,26 +19,61 @@ module.exports = [ "label": "Enabled" }, { - "id": "pavlok_api_key", - "type": "input", - "messageKey": "PKJS_PAVLOK_API_KEY", - "label": "API Key" + "id": "pavlok_api_key", + "type": "input", + "messageKey": "PKJS_PAVLOK_API_KEY", + "label": "API Key" }, { "id": "pavlok_zap_intensity", "type": "slider", "messageKey": "PKJS_PAVLOK_ZAP_INTENSITY", - defaultValue: 30, - min: 5, - max: 100, - step: 5, + "defaultValue": 30, + "min": 5, + "max": 100, + "step": 5, "label": "Zap Intensity" }, { - "id": "pavlok_test_button", - "type": "button", - "messageKey": "PKJS_PAVLOK_TEST_BUTTON", - "defaultValue": "Test Beep" + "id": "pavlok_test_button", + "type": "button", + "messageKey": "PKJS_PAVLOK_TEST_BUTTON", + "defaultValue": "Test Beep" + } + ] + }, + { + "type": "section", + "items": [ + { + "type": "heading", + "size": 3, + "defaultValue": "Awareness Alarm" + }, + { + "id": "awareness_time_gap", + "type": "slider", + "messageKey": "PKJS_AWARENESS_TIME_GAP", + "defaultValue": 9, + "min": 1, + "max": 30, + "label": "Gap From Main Alarm (Minutes)" + }, + { + "id": "awareness_time_limit", + "type": "slider", + "messageKey": "PKJS_AWARENESS_TIME_LIMIT", + "defaultValue": 30, + "min": 3, + "max": 120, + "label": "Time Limit (Seconds)" + }, + { + "id": "awareness_nudge_enable", + "type": "toggle", + "messageKey": "PKJS_AWARENESS_NUDGE_ENABLE", + "defaultValue": true, + "label": "Awareness Nudge" } ] },