From ce0a8ee4feb6d334fe7b4541aec49d7e6db2264f Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Wed, 19 Aug 2026 22:31:16 -0400 Subject: [PATCH] Add zap step mode (zaps get stronger as more awareness alarms are failed); remove nudge setting (nudge is always on now); fix nudge being missed if wakeup occurs while in setter; fix force-exiting an awareness alarm triggering a main alarm --- package.json | 6 ++- src/c/main.c | 119 +++++++++++++++++++++++++---------------- src/c/scheduler.h | 3 +- src/c/settings.h | 5 +- src/pkjs/config.js | 63 ++++++++++++++-------- src/pkjs/customClay.js | 14 +++-- src/pkjs/index.js | 2 +- 7 files changed, 135 insertions(+), 77 deletions(-) diff --git a/package.json b/package.json index 791c806..d4edc50 100644 --- a/package.json +++ b/package.json @@ -34,11 +34,13 @@ "messageKeys": [ "PKJS_READY", "PKJS_PAVLOK_ENABLE", + "PKJS_PAVLOK_ZAP_MIN", + "PKJS_PAVLOK_ZAP_STEP", + "PKJS_PAVLOK_ZAP_MAX", "PKJS_PAVLOK_ZAP_INTERVAL", "PKJS_PAVLOK_ZAP", "PKJS_AWARENESS_TIME_GAP", - "PKJS_AWARENESS_TIME_LIMIT", - "PKJS_AWARENESS_NUDGE_ENABLE" + "PKJS_AWARENESS_TIME_LIMIT" ], "resources": { "media": [ diff --git a/src/c/main.c b/src/c/main.c index e65bf24..31acf9d 100644 --- a/src/c/main.c +++ b/src/c/main.c @@ -62,7 +62,8 @@ static uint8_t s_solution_indices[3] = {8, 8, 8}; static uint8_t s_selected_indices[3] = {9, 9, 9}; static uint8_t s_awareness_countdown_int = 0; static char s_awareness_countdown_buf[4]; -static uint8_t s_zap_repeat_countdown = 0; +static uint8_t s_awareness_zap_repeat_countdown = 0; +static uint8_t s_awareness_zap_intensity = 0; static bool s_hide_countdown = true; static uint32_t s_vibe_segments[5]; static VibePattern s_vibe_pattern; @@ -195,13 +196,22 @@ static void inbox_received_handler(DictionaryIterator *iter, void *context) { } apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ENABLE, &settings.PavlokEnabled); apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ZAP_INTERVAL, &settings.PavlokIntervalSeconds); + apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ZAP_MIN, &settings.PavlokMin); + apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ZAP_STEP, &settings.PavlokStep); + apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ZAP_MAX, &settings.PavlokMax); 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); + if (settings.PavlokStep == 0) { + // match min and max values if disabling step mode, as the max is used for force exit/overwrite punishments + settings.PavlokMax = settings.PavlokMin; + } else if (settings.PavlokMax <= settings.PavlokMin) { + // disable step mode if max is not higher than min + settings.PavlokStep = 0; + } persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings)); } -static void send_zap() { +static void send_zap(uint8_t intensity) { if (!settings.PavlokEnabled) { return; } @@ -211,7 +221,7 @@ static void send_zap() { if (result != APP_MSG_OK) { return; } - dict_write_uint8(out, MESSAGE_KEY_PKJS_PAVLOK_ZAP, 1); + dict_write_uint8(out, MESSAGE_KEY_PKJS_PAVLOK_ZAP, intensity); result = app_message_outbox_send(); if (result != APP_MSG_OK) { return; @@ -225,16 +235,6 @@ static bool schedule_alarm(WakeupReason schedule_reason) { WakeupId status; switch (schedule_reason) { - case WAKE_RECOVERY_ALARM: - target = time(NULL) + 15; - // 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); - break; - case WAKE_AWARENESS_ALARM: - target = time(NULL) + settings.AwarenessTimeGapMinutes * SECONDS_PER_MINUTE; - wakeup_cancel_all(); - status = wakeup_schedule_simple_robust(target, SECONDS_PER_MINUTE, schedule_reason); - break; case WAKE_MAIN_ALARM: { time_t now = time(NULL); struct tm target_tm = *localtime(&now); @@ -247,7 +247,28 @@ static bool schedule_alarm(WakeupReason schedule_reason) { } wakeup_cancel_all(); status = wakeup_schedule_simple_robust(target, SECONDS_PER_MINUTE, schedule_reason); + if (settings.PavlokEnabled) { + persist_write_int(STORAGE_KEY_PAVLOK_INTENSITY, settings.PavlokMin); + } + break; } + case WAKE_AWARENESS_ALARM: + target = time(NULL) + settings.AwarenessTimeGapMinutes * SECONDS_PER_MINUTE; + wakeup_cancel_all(); + status = wakeup_schedule_simple_robust(target, SECONDS_PER_MINUTE, schedule_reason); + if (settings.PavlokEnabled && (s_alarm_reason == WAKE_AWARENESS_ALARM || s_alarm_reason == WAKE_RECOVERY_AWARENESS_ALARM)) { + uint8_t next_intensity = s_awareness_zap_intensity + settings.PavlokStep; + if (next_intensity <= settings.PavlokMax && next_intensity <= 100) { + // only boost next intensity if it is within the allowed bounds + persist_write_int(STORAGE_KEY_PAVLOK_INTENSITY, s_awareness_zap_intensity + settings.PavlokStep); + } + } + break; + case WAKE_RECOVERY_MAIN_ALARM: + case WAKE_RECOVERY_AWARENESS_ALARM: + target = time(NULL) + 15; + // no need to cancel alarms, as recovery alarms short-lived and is expired by the time another is being scheduled + status = wakeup_schedule_simple_robust(target, 15, schedule_reason); } if (status < 0) { push_status_window(STATUS_SET_FAILURE); @@ -295,15 +316,15 @@ static void awareness_alarm_seconds_handler(struct tm *tick_time, TimeUnits unit 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(); - s_zap_repeat_countdown = settings.PavlokIntervalSeconds; + send_zap(s_awareness_zap_intensity); + s_awareness_zap_repeat_countdown = settings.PavlokIntervalSeconds; } } else { - if (s_zap_repeat_countdown > 0) { - s_zap_repeat_countdown--; - if (s_zap_repeat_countdown == 0) { - send_zap(); - s_zap_repeat_countdown = settings.PavlokIntervalSeconds; + if (s_awareness_zap_repeat_countdown > 0) { + s_awareness_zap_repeat_countdown--; + if (s_awareness_zap_repeat_countdown == 0) { + send_zap(s_awareness_zap_intensity); + s_awareness_zap_repeat_countdown = settings.PavlokIntervalSeconds; } } layer_set_hidden(text_layer_get_layer(s_countdown_txt_layer), s_hide_countdown); @@ -396,8 +417,9 @@ static void drop_menu_window_unload(Window *window) { } static void live_wakeup_handler_for_alarm(WakeupId id, int32_t wakeup_reason) { - if (wakeup_reason == WAKE_RECOVERY_ALARM) { // if recovery alarm, re-schedule it - schedule_alarm(WAKE_RECOVERY_ALARM); // output is not captured, as it is not actionable + if (wakeup_reason == WAKE_RECOVERY_MAIN_ALARM || wakeup_reason == WAKE_RECOVERY_AWARENESS_ALARM) { + // if recovery alarm, re-schedule it + schedule_alarm(wakeup_reason); } } @@ -623,13 +645,10 @@ static void alarm_window_load() { 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); - // this code forces recovery alarms to act as main alarms, even if the force quit was an awareness alarm; - // this is a "bug" I'm willing to accept for the sake of simplicity - all it does is punish the user more - // for force-quitting - if (s_alarm_reason == WAKE_AWARENESS_ALARM) { + if (s_alarm_reason == WAKE_AWARENESS_ALARM || s_alarm_reason == WAKE_RECOVERY_AWARENESS_ALARM) { text_layer_set_font(s_countdown_txt_layer, s_font_lecoish_small); s_awareness_countdown_int = settings.AwarenessTimeLimitSeconds; - s_zap_repeat_countdown = 0; + s_awareness_zap_repeat_countdown = 0; 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); } else { @@ -649,10 +668,11 @@ static void alarm_window_load() { .num_segments = ARRAY_LENGTH(s_vibe_segments), }; - schedule_alarm(WAKE_RECOVERY_ALARM); // output is not captured, as it is not actionable - if (s_alarm_reason == WAKE_AWARENESS_ALARM) { + if (s_alarm_reason == WAKE_AWARENESS_ALARM || s_alarm_reason == WAKE_RECOVERY_AWARENESS_ALARM) { + schedule_alarm(WAKE_RECOVERY_AWARENESS_ALARM); tick_timer_service_subscribe(SECOND_UNIT, awareness_alarm_seconds_handler); } else { + schedule_alarm(WAKE_RECOVERY_MAIN_ALARM); tick_timer_service_subscribe(SECOND_UNIT, main_alarm_seconds_handler); } } @@ -758,6 +778,23 @@ static void alarm_click_config_provider(void *ctx) { } static void push_alarm_window() { + s_alarm_reason = persist_read_int(STORAGE_KEY_ALARM_REASON); + + switch (s_alarm_reason) { + case WAKE_RECOVERY_MAIN_ALARM: + send_zap(settings.PavlokMax); + break; + case WAKE_RECOVERY_AWARENESS_ALARM: + send_zap(settings.PavlokMax); + [[fallthrough]]; + case WAKE_AWARENESS_ALARM: + s_awareness_zap_intensity = persist_read_int(STORAGE_KEY_PAVLOK_INTENSITY); + vibes_short_pulse(); + break; + default: + break; + } + window_set_window_handlers( s_window, (WindowHandlers){.load = alarm_window_load, .unload = alarm_window_unload}); @@ -867,7 +904,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) { if (s_alarm_reason != WAKE_MAIN_ALARM) { - send_zap(); + send_zap(settings.PavlokMax); vibes_double_pulse(); return; // early return to avoid overwriting recovery/awareness alarm } @@ -910,7 +947,6 @@ static void setter_click_config_provider(void *ctx) { } static void live_wakeup_handler_for_setter(WakeupId id, int32_t reason) { - s_alarm_reason = persist_read_int(STORAGE_KEY_ALARM_REASON); s_highlight_pos = 0; window_stack_pop_all(false); push_alarm_window(); @@ -981,6 +1017,8 @@ static void setter_window_unload() { } static void push_setter_window() { + s_alarm_reason = persist_read_int(STORAGE_KEY_ALARM_REASON); + window_set_window_handlers( s_window, (WindowHandlers){.load = setter_window_load, .unload = setter_window_unload}); @@ -1007,26 +1045,15 @@ static void init() { settings.TutorialEnabled = true; settings.PavlokEnabled = false; settings.PavlokIntervalSeconds = 10; + settings.PavlokMin = 30; + settings.PavlokStep = 10; + settings.PavlokMax = 60; 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: - 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; default: diff --git a/src/c/scheduler.h b/src/c/scheduler.h index d61e126..491e919 100644 --- a/src/c/scheduler.h +++ b/src/c/scheduler.h @@ -5,7 +5,8 @@ typedef enum { WAKE_MAIN_ALARM = 0, WAKE_AWARENESS_ALARM = 1, - WAKE_RECOVERY_ALARM = 2 + WAKE_RECOVERY_MAIN_ALARM = 2, + WAKE_RECOVERY_AWARENESS_ALARM = 3 } WakeupReason; typedef struct TheTime { diff --git a/src/c/settings.h b/src/c/settings.h index 1706a46..05884ae 100644 --- a/src/c/settings.h +++ b/src/c/settings.h @@ -8,14 +8,17 @@ #define STORAGE_KEY_ALARM_HOUR 0 #define STORAGE_KEY_ALARM_MINUTE 1 +#define STORAGE_KEY_PAVLOK_INTENSITY 2 typedef struct AppSettings { bool TutorialEnabled; bool PavlokEnabled; + uint8_t PavlokMin; + uint8_t PavlokStep; + uint8_t PavlokMax; uint8_t PavlokIntervalSeconds; 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 796d1d8..e85a5ab 100644 --- a/src/pkjs/config.js +++ b/src/pkjs/config.js @@ -16,23 +16,54 @@ module.exports = [ "type": "toggle", "messageKey": "PKJS_PAVLOK_ENABLE", "defaultValue": false, - "label": "Enabled" + "label": "Enabled", + "description": "Toggle integration w/Pavlok shockers." }, { "id": "pavlok_api_key", "type": "input", "messageKey": "PKJS_PAVLOK_API_KEY", - "label": "API Key" + "label": "API Key", + "description": 'API bearer token, starting with "ey".' }, { - "id": "pavlok_zap_intensity", + "id": "pavlok_test_beep", + "type": "button", + "messageKey": "PKJS_PAVLOK_TEST_BEEP", + "defaultValue": "Test API Key w/Beep" + }, + { + "id": "pavlok_zap_min", "type": "slider", - "messageKey": "PKJS_PAVLOK_ZAP_INTENSITY", + "messageKey": "PKJS_PAVLOK_ZAP_MIN", "defaultValue": 30, "min": 5, "max": 100, "step": 5, - "label": "Zap Intensity" + "label": "Zap Intensity (Base)", + "description": "% intensity of base zap." + }, + { + "id": "pavlok_zap_step", + "type": "slider", + "messageKey": "PKJS_PAVLOK_ZAP_STEP", + "defaultValue": 10, + "min": 0, + "max": 100, + "step": 5, + "label": "Zap Step", + "description": "% to increase base zap intensity by after each failed awareness alarm; set to 0 to disable." + }, + { + "id": "pavlok_zap_max", + "type": "slider", + "messageKey": "PKJS_PAVLOK_ZAP_MAX", + "defaultValue": 60, + "min": 10, + "max": 100, + "step": 5, + "label": "Zap Intensity (Max)", + "description": "% intensity of maximum zap; only takes effect if greater than base and step is enabled. Also used for force exit/alarm overwrite penalties." }, { "id": "pavlok_zap_interval", @@ -42,13 +73,8 @@ module.exports = [ "min": 1, "max": 60, "step": 1, - "label": "Zap Repeat Interval" - }, - { - "id": "pavlok_test_button", - "type": "button", - "messageKey": "PKJS_PAVLOK_TEST_BUTTON", - "defaultValue": "Test Beep" + "label": "Zap Repeat Interval", + "description": "Seconds between zaps after awareness alarm counter expires." } ] }, @@ -67,7 +93,8 @@ module.exports = [ "defaultValue": 9, "min": 1, "max": 30, - "label": "Gap From Main Alarm (Minutes)" + "label": "Gap (Minutes)", + "description": "Time gap between main alarm and subsequent awareness alarms." }, { "id": "awareness_time_limit", @@ -76,14 +103,8 @@ module.exports = [ "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" + "label": "Time Limit (Seconds)", + "description": "Time limit the awareness alarm must be solved within to disarm Trigalarm." } ] }, diff --git a/src/pkjs/customClay.js b/src/pkjs/customClay.js index 3d5d0a0..4b2fce0 100644 --- a/src/pkjs/customClay.js +++ b/src/pkjs/customClay.js @@ -7,14 +7,18 @@ module.exports = function (minified) { function togglePavlokIntegration() { if (this.get()) { clayConfig.getItemById('pavlok_api_key').show(); - clayConfig.getItemById('pavlok_zap_intensity').show(); + clayConfig.getItemById('pavlok_test_beep').show(); + clayConfig.getItemById('pavlok_zap_min').show(); + clayConfig.getItemById('pavlok_zap_step').show(); + clayConfig.getItemById('pavlok_zap_max').show(); clayConfig.getItemById('pavlok_zap_interval').show(); - clayConfig.getItemById('pavlok_test_button').show(); } else { clayConfig.getItemById('pavlok_api_key').hide(); - clayConfig.getItemById('pavlok_zap_intensity').hide(); + clayConfig.getItemById('pavlok_test_beep').hide(); + clayConfig.getItemById('pavlok_zap_min').hide(); + clayConfig.getItemById('pavlok_zap_step').hide(); + clayConfig.getItemById('pavlok_zap_max').hide(); clayConfig.getItemById('pavlok_zap_interval').hide(); - clayConfig.getItemById('pavlok_test_button').hide(); } } @@ -40,6 +44,6 @@ module.exports = function (minified) { togglePavlokIntegration.call(pavlokIntegrationToggle); pavlokIntegrationToggle.on('change', togglePavlokIntegration); - clayConfig.getItemById('pavlok_test_button').on('click', sendTestBeep); + clayConfig.getItemById('pavlok_test_beep').on('click', sendTestBeep); }); }; diff --git a/src/pkjs/index.js b/src/pkjs/index.js index e03f260..74d108c 100644 --- a/src/pkjs/index.js +++ b/src/pkjs/index.js @@ -11,7 +11,7 @@ Pebble.addEventListener("ready", function () { Pebble.addEventListener("appmessage", function (dict) { if (dict.payload["PKJS_PAVLOK_ZAP"]) { const cfg = JSON.parse(localStorage.getItem('clay-settings')); - callAPI(cfg.PKJS_PAVLOK_API_KEY, cfg.PKJS_PAVLOK_ZAP_INTENSITY); + callAPI(cfg.PKJS_PAVLOK_API_KEY, parseInt(dict.payload["PKJS_PAVLOK_ZAP"])); } });