Make awareness alarm configurable and vastly more useful

This commit is contained in:
2026-08-18 23:02:55 -04:00
parent 8e64b4b4b8
commit dc10841812
5 changed files with 164 additions and 77 deletions
+4 -1
View File
@@ -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": [
+93 -58
View File
@@ -1,17 +1,21 @@
#include "main.h"
#include "scheduler.h"
#include "settings.h"
#include "triangles.h"
#include <pebble.h>
#include <stdio.h>
#include <stdlib.h>
// 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;
-6
View File
@@ -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,
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <stdint.h>
// 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!");
+39 -4
View File
@@ -28,10 +28,10 @@ module.exports = [
"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"
},
{
@@ -42,6 +42,41 @@ module.exports = [
}
]
},
{
"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"
}
]
},
{
"type": "submit",
"defaultValue": "Save Settings"