Compare commits

..
5 Commits
11 changed files with 246 additions and 394 deletions

No files matched your search

+14 -15
View File
@@ -6,28 +6,27 @@
### Simple
The alarm setter is only one page. You can only set one alarm. There is no snooze, there is no weekly schedule, and there is no way to disable the alarm (you'll always have one set - every day - like it or not).
### Persistent
The alarm goes off at least twice. The first time, you must solve a triangle to silence the alarm. The second time, you must do it again, but within a strict time limit (and no constant vibration). Fail to solve it within the time limit? The alarm is scheduled for a third time. This process continues until you solve within the time limit - this is to prove you managed to stay awake between alarms before allowing it to be fully disarmed. Even if you force-quit Trigalarm, it will shortly re-open to continue nagging you.
The alarm cannot be disabled until the user solves their configured number of triangles. Each triangle comes with a time limit. Failure to solve a triangle within the time limit will demote the user to the previous triangle.
Guards are in place to prevent the user from force-quitting/resetting the alarm when an alarm is meant to be active.
### Adaptable
Trigalarm is meant to be adapted to your needs, and you should adjust its settings as you progress on your sleep journey. For example, as your trig skills improve, you may want to lower the time limit for the disarming alarm. You can also adjust the gap between alarms to account for how quickly you find yourself getting out of bed.
Perhaps the most unique, Trigalarm features optional [Pavlok](https://pavlok.com/pages/how-it-works) API integration to punish you via minor electrocution if you fall back asleep, force-quit the app, or attempt to overwrite your active alarm cycle.
## Learn the Lingo
- Main alarm
- The first alarm that goes off; comes with continuous vibration and does not disarm Trigalarm when solved. If Pavlok integration is enabled, this alarm can zap the user after a configurable amount of time if they do not respond. Solving this alarm will schedule an awareness alarm.
- Awareness alarm
- Any subsequent alarm after the main alarm has been solved; comes with only one brief vibration to indicate to the user it has opened. It has a configurable time limit it must be solved within to disarm Trigalarm. If the time limit expires, it will begin vibrations (and optionally Pavlok zaps), and once solved, it will schedule another awareness alarm.
Trigalarm is meant to be adapted to your needs with as little confusion as possible. There are three powerful settings:
1. Number of triangles to solve in order to disarm the alarm
2. The time limit for the final (fastest) triangle
- Time limits for prior triangles are calculated automatically based on this one
3. Pavlok integration (API key + zap intensity)
- Punishes the user via mild electrocution if they...
- ...take too long to respond to the first triangle
- ...get demoted from a higher level triangle
- ...force quit the app by holding the back button (the app re-opens and sends a zap)
## Tips & Tricks
- When you successfully disarm the alarm, you are prompted with the options to either exit and keep the same alarm time for tomorrow, or to select a new time. If choosing the latter, whatever time you select will assume you mean tomorrow. This avoids situations where you wake up at 6 on a Friday, set an alarm intended for 9 on Saturday, and end up with your wrist buzzing in three hours!
- If you are stuck waiting for an awareness alarm or there is a bug that schedules an alarm incorrectly, there is a "Reset Alarm" button in the phone-side settings that will set your alarm to 00:00.
- There is a "Reset Alarm" button in the phone-side settings to help if you make an unsolvable alarm configuration by mistake.
## Some Neat Stuff
- The vibration pattern is randomized. It derives the length of the three pulses from the angles in the generated triangle. Neat!
- Some of the alarm scheduling code was taken from [Gentle Wake](https://github.com/SeaPea/GentleWake). It seems trusted by the community, so... I stole it! The beauty of open source, no AI needed!
- A custom font was made to satisfy the need for something that looks like LECO but is monospaced. The version I made contains only numbers, period, and colon, but if you want to build off of it, [it's public](https://git.wetfence.xyz/RandyTheSilly/pebble-misc/src/branch/main/fonts) and easy to modify!
## Future Goals (1.1 and beyond)
- Full touch navigation
- Draw measurements based on triangle side locations, rather than in fixed positions
## Future Goals (2.1 and beyond)
- Custom, self-hosted Pavlok API support (going to try some Bluetooth reverse engineering)
+6 -10
View File
@@ -1,7 +1,7 @@
{
"name": "trigalarm",
"author": "RandyTheSilly",
"version": "1.0.0",
"version": "2.0.0",
"keywords": [
"alarm",
"math",
@@ -17,7 +17,7 @@
],
"private": true,
"dependencies": {
"@rebble/clay": "^1.0.10"
"@rebble/clay": "^1.1.0"
},
"pebble": {
"displayName": "Trigalarm",
@@ -34,15 +34,11 @@
"messageKeys": [
"PKJS_READY",
"PKJS_ALARM_RESET",
"PKJS_TRIANGLE_COUNT",
"PKJS_TRIANGLE_MIN_SECS",
"PKJS_PAVLOK_ENABLE",
"PKJS_PAVLOK_ZAP_MIN",
"PKJS_PAVLOK_ZAP_MAX",
"PKJS_PAVLOK_ZAP_STEP",
"PKJS_PAVLOK_ZAP_MAIN_DELAY",
"PKJS_PAVLOK_ZAP_INTERVAL",
"PKJS_PAVLOK_ZAP",
"PKJS_AWARENESS_TIME_GAP",
"PKJS_AWARENESS_TIME_LIMIT"
"PKJS_PAVLOK_ZAP_INTENSITY",
"PKJS_PAVLOK_ZAP"
],
"resources": {
"media": [
+174 -267
View File
@@ -54,19 +54,16 @@ static GBitmap *s_clock_bmp;
static GBitmap *s_check_bmp;
// global vars
static WakeupReason s_alarm_reason;
static GColor s_setter_highlight_color;
static uint8_t s_highlight_pos = 0;
static bool s_skip_current_day = false;
//// alarm
static WakeupReason s_alarm_reason;
static uint8_t s_current_triangle_num;
static uint8_t s_solution_indices[3];
static uint8_t s_selected_indices[3] = {9, 9, 9};
static uint8_t s_main_countdown_till_zap;
static uint8_t s_awareness_countdown_int = 0;
static char s_awareness_countdown_buf[4];
static uint8_t s_alarm_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;
@@ -94,16 +91,9 @@ static char *s_options_under[2];
////////////////////// STATUS //////////////////////
static void click_handler_disable_tutorial(void *recognizer, void *ctx) {
settings.TutorialEnabled = false;
persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings));
window_stack_pop_all(true);
}
static void click_handler_push_setter(void *recognizer, void *ctx) {
s_highlight_pos = 0;
s_skip_current_day = true; // setting the next alarm should assume the user meant tomorrow
s_alarm_reason = WAKE_MAIN_ALARM; // going back to setter means alarm was disarmed or failed to set; do not punish user
window_stack_pop_all(true);
push_setter_window();
}
@@ -116,11 +106,6 @@ static void click_handler_retry(void *recognizer, void *ctx) {
window_stack_pop(true);
}
static void status_main_tutorial_click_config_provider(void *ctx) {
window_single_click_subscribe(BUTTON_ID_SELECT, click_handler_exit);
window_single_click_subscribe(BUTTON_ID_BACK, click_handler_disable_tutorial);
}
static void status_confirmation_click_config_provider(void *ctx) {
window_single_click_subscribe(BUTTON_ID_BACK, click_handler_push_setter);
window_single_click_subscribe(BUTTON_ID_SELECT, click_handler_exit);
@@ -143,11 +128,6 @@ static void status_window_load(StatusWindowPurpose purpose) {
text_layer_set_font(s_status_txt_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24));
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, "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: {
static char s_status_confirmed_buffer[106];
snprintf(s_status_confirmed_buffer, sizeof(s_status_confirmed_buffer), "Alarm disarmed and re-set (%02u:%02u) successfully.\n\nPress SELECT to exit or BACK to change the alarm time.", the_time.HourSet, the_time.MinuteSet);
@@ -209,40 +189,26 @@ static bool schedule_alarm(WakeupReason schedule_reason) {
WakeupId status;
switch (schedule_reason) {
case WAKE_MAIN_ALARM: {
case WAKE_ALARM: {
target = compute_alarm_time(the_time.HourWIP, the_time.MinuteWIP);
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:
case WAKE_RECOVERY_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
// no need to cancel alarms, as recovery alarms are short-lived and are 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);
persist_write_int(STORAGE_KEY_ALARM_REASON, WAKE_MAIN_ALARM);
persist_write_int(STORAGE_KEY_ALARM_REASON, WAKE_ALARM);
s_alarm_reason = WAKE_ALARM;
return false;
}
if (persist_read_int(STORAGE_KEY_ALARM_REASON) != schedule_reason) {
persist_write_int(STORAGE_KEY_ALARM_REASON, schedule_reason);
s_alarm_reason = schedule_reason;
}
return true;
}
@@ -289,25 +255,15 @@ static void inbox_received_handler(DictionaryIterator *iter, void *context) {
if (dict_find(iter, MESSAGE_KEY_PKJS_ALARM_RESET)) {
the_time.HourWIP = 0;
the_time.MinuteWIP = 0;
s_alarm_reason = WAKE_MAIN_ALARM;
schedule_alarm(WAKE_MAIN_ALARM);
s_alarm_reason = WAKE_ALARM;
schedule_alarm(WAKE_ALARM);
return;
}
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_MAX, &settings.PavlokMax);
apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ZAP_STEP, &settings.PavlokStep);
apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ZAP_MAIN_DELAY, &settings.PavlokMainDelay);
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);
if (settings.PavlokMax <= settings.PavlokMin) {
// disable step mode if max is not higher than min
settings.PavlokStep = 0;
// ensure force quit/overwrite punishments aren't weaker than base
settings.PavlokMax = settings.PavlokMin;
}
apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ZAP_INTENSITY, &settings.PavlokIntensity);
apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_TRIANGLE_COUNT, &settings.TriangleCount);
apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_TRIANGLE_MIN_SECS, &settings.TriangeMinSecs);
persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings));
}
@@ -330,43 +286,13 @@ static void send_zap(uint8_t intensity) {
////////////////////// ALARM //////////////////////
static void main_alarm_seconds_handler(struct tm *tick_time, TimeUnits units_changed) {
if (s_main_countdown_till_zap > 0) {
s_main_countdown_till_zap--;
if (s_main_countdown_till_zap == 0) {
send_zap(settings.PavlokMin);
s_alarm_zap_repeat_countdown = settings.PavlokIntervalSeconds;
}
} else if (s_alarm_zap_repeat_countdown > 0) {
s_alarm_zap_repeat_countdown--;
if (s_alarm_zap_repeat_countdown == 0) {
send_zap(settings.PavlokMin);
s_alarm_zap_repeat_countdown = settings.PavlokIntervalSeconds;
}
}
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(s_awareness_zap_intensity);
s_alarm_zap_repeat_countdown = settings.PavlokIntervalSeconds;
}
} else {
if (s_alarm_zap_repeat_countdown > 0) {
s_alarm_zap_repeat_countdown--;
if (s_alarm_zap_repeat_countdown == 0) {
send_zap(s_awareness_zap_intensity);
s_alarm_zap_repeat_countdown = settings.PavlokIntervalSeconds;
}
}
layer_set_hidden(text_layer_get_layer(s_countdown_txt_layer), s_hide_countdown);
s_hide_countdown = !s_hide_countdown;
vibes_enqueue_custom_pattern(s_vibe_pattern);
send_zap(settings.PavlokIntensity);
// TODO demote and reset countdown int
}
}
@@ -447,20 +373,15 @@ static void alarm_select_hold_handler(void *recognizer, void *ctx) {
wakeup_service_subscribe(NULL);
if (s_awareness_countdown_int > 0) {
// set main alarm if the awareness alarm was solved within time limit
// TODO promote OR disarm
// if (s_current_triangle == settings.TriangleCount) {
tick_timer_service_unsubscribe(); // alarm is fully disarmed - prevent stimulus in confirmation window
if (schedule_alarm(WAKE_MAIN_ALARM)) {
if (schedule_alarm(WAKE_ALARM)) {
push_status_window(STATUS_CONFIRMATION);
}
} else {
// otherwise, set awareness alarm and exit
if (schedule_alarm(WAKE_AWARENESS_ALARM)) {
if (settings.TutorialEnabled && (s_alarm_reason == WAKE_MAIN_ALARM || s_alarm_reason == WAKE_RECOVERY_MAIN_ALARM)) {
push_status_window(STATUS_MAIN_TUTORIAL);
} else {
window_stack_pop_all(true);
}
}
// } else {
// GENERATE NEW TRIANLGE AND SET TIME LIMIT
// }
}
}
@@ -529,12 +450,143 @@ 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_MAIN_ALARM || wakeup_reason == WAKE_RECOVERY_AWARENESS_ALARM) {
if (wakeup_reason == WAKE_RECOVERY_ALARM) {
// if recovery alarm, re-schedule it
schedule_alarm(wakeup_reason);
}
}
static void update_for_new_triangle() {
// pick & display 2 side lengths to the user
static char side_a_buffer[8], side_b_buffer[8], side_c_buffer[8];
uint8_t ignored_side = rand() % 3;
uint8_t sin_index = rand() % 2;
uint8_t cos_index = rand() % 2;
uint8_t tan_index = rand() % 2;
switch (ignored_side) {
case 0:
snprintf(side_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b);
snprintf(side_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c);
s_options_over[0] = side_b_buffer;
s_options_over[1] = side_c_buffer;
s_options_under[0] = side_b_buffer;
s_options_under[1] = side_c_buffer;
// store solution
s_solution_indices[0] = 2; // tan/cot
if (s_triangle_layer->theta_angle_is_left) {
if (tan_index == 0) { // tan
s_solution_indices[1] = 0; // b (opposite)
s_solution_indices[2] = 1; // c (adjacent)
} else { // cot
s_solution_indices[1] = 1; // c (adjacent)
s_solution_indices[2] = 0; // b (opposite)
}
} else {
if (tan_index == 0) { // tan
s_solution_indices[1] = 1; // c (opposite)
s_solution_indices[2] = 0; // b (adjacent)
} else { // cot
s_solution_indices[1] = 0; // b (adjacent)
s_solution_indices[2] = 1; // c (opposite)
}
}
break;
case 1:
snprintf(side_a_buffer, sizeof(side_a_buffer), "%u", s_triangle_layer->side_a);
snprintf(side_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c);
s_options_over[0] = side_a_buffer;
s_options_over[1] = side_c_buffer;
s_options_under[0] = side_a_buffer;
s_options_under[1] = side_c_buffer;
// store solution
if (s_triangle_layer->theta_angle_is_left) {
s_solution_indices[0] = 1; // cos/sec
if (cos_index == 0) { // cos
s_solution_indices[1] = 1; // c (adjacent)
s_solution_indices[2] = 0; // a (hypotenuse)
} else { // sec
s_solution_indices[1] = 0; // a (hypotenuse)
s_solution_indices[2] = 1; // c (adjacent)
}
} else {
s_solution_indices[0] = 0; // sin/csc
if (sin_index == 0) { // sin
s_solution_indices[1] = 1; // c (opposite)
s_solution_indices[2] = 0; // a (hypotenuse)
} else { // csc
s_solution_indices[1] = 0; // a (hypotenuse)
s_solution_indices[2] = 1; // c (opposite)
}
}
break;
case 2:
snprintf(side_a_buffer, sizeof(side_a_buffer), "%u", s_triangle_layer->side_a);
snprintf(side_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b);
s_options_over[0] = side_a_buffer;
s_options_over[1] = side_b_buffer;
s_options_under[0] = side_a_buffer;
s_options_under[1] = side_b_buffer;
// store solution
if (s_triangle_layer->theta_angle_is_left) {
s_solution_indices[0] = 0; // sin/csc
if (sin_index == 0) { // sin
s_solution_indices[1] = 1; // b (opposite)
s_solution_indices[2] = 0; // a (hypotenuse)
} else { // csc
s_solution_indices[1] = 0; // a (hypotenuse)
s_solution_indices[2] = 1; // b (opposite)
}
} else {
s_solution_indices[0] = 1;
if (cos_index == 0) { // cos
s_solution_indices[1] = 1; // b (adjacent)
s_solution_indices[2] = 0; // a (hypotenuse)
} else { // sec
s_solution_indices[1] = 0; // a (hypotenuse)
s_solution_indices[2] = 1; // b (adjacent)
}
}
}
text_layer_set_text(s_side_a_txt_layer, side_a_buffer);
text_layer_set_text(s_side_b_txt_layer, side_b_buffer);
text_layer_set_text(s_side_c_txt_layer, side_c_buffer);
// set trigonometric function options
s_sin = s_sin_arr[sin_index];
s_cos = s_cos_arr[cos_index];
s_tan = s_tan_arr[tan_index];
s_options_function[0] = s_sin;
s_options_function[1] = s_cos;
s_options_function[2] = s_tan;
// setup vibes
static const uint32_t VIBE_ON_FLOOR = 120; // ensure motor is on long enough to be felt
static const uint32_t VIBE_ON_SCALE = 4;
static const uint32_t VIBE_GAP = 40;
s_vibe_segments[0] = VIBE_ON_FLOOR + (uint32_t)s_triangle_layer->angle_A * VIBE_ON_SCALE;
s_vibe_segments[1] = VIBE_GAP;
s_vibe_segments[2] = VIBE_ON_FLOOR + (uint32_t)s_triangle_layer->angle_B * VIBE_ON_SCALE;
s_vibe_segments[3] = VIBE_GAP;
s_vibe_segments[4] = VIBE_ON_FLOOR + (uint32_t)s_triangle_layer->angle_C * VIBE_ON_SCALE;
s_vibe_pattern = (VibePattern){
.durations = s_vibe_segments,
.num_segments = ARRAY_LENGTH(s_vibe_segments),
};
// setup countdown
// TODO vary time based on progress:
// Store time step per triangle
// Determine time remaining from min+(s_total_count-s_current_triangle)*step
s_awareness_countdown_int = settings.TriangeMinSecs;
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);
}
static void alarm_window_load() {
wakeup_service_subscribe(live_wakeup_handler_for_alarm);
@@ -638,159 +690,31 @@ static void alarm_window_load() {
text_layer_set_background_color(s_side_c_txt_layer, GColorClear);
text_layer_set_font(s_side_c_txt_layer, s_font_mansalva_small);
layer_add_child(window_layer, text_layer_get_layer(s_side_c_txt_layer));
//// display 2 side lengths to the user
static char side_a_buffer[8], side_b_buffer[8], side_c_buffer[8];
uint8_t ignored_side = rand() % 3;
uint8_t sin_index = rand() % 2;
uint8_t cos_index = rand() % 2;
uint8_t tan_index = rand() % 2;
switch (ignored_side) {
case 0:
snprintf(side_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b);
snprintf(side_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c);
s_options_over[0] = side_b_buffer;
s_options_over[1] = side_c_buffer;
s_options_under[0] = side_b_buffer;
s_options_under[1] = side_c_buffer;
// store solution
s_solution_indices[0] = 2; // tan/cot
if (s_triangle_layer->theta_angle_is_left) {
if (tan_index == 0) { // tan
s_solution_indices[1] = 0; // b (opposite)
s_solution_indices[2] = 1; // c (adjacent)
} else { // cot
s_solution_indices[1] = 1; // c (adjacent)
s_solution_indices[2] = 0; // b (opposite)
}
} else {
if (tan_index == 0) { // tan
s_solution_indices[1] = 1; // c (opposite)
s_solution_indices[2] = 0; // b (adjacent)
} else { // cot
s_solution_indices[1] = 0; // b (adjacent)
s_solution_indices[2] = 1; // c (opposite)
}
}
break;
case 1:
snprintf(side_a_buffer, sizeof(side_a_buffer), "%u", s_triangle_layer->side_a);
snprintf(side_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c);
s_options_over[0] = side_a_buffer;
s_options_over[1] = side_c_buffer;
s_options_under[0] = side_a_buffer;
s_options_under[1] = side_c_buffer;
// store solution
if (s_triangle_layer->theta_angle_is_left) {
s_solution_indices[0] = 1; // cos/sec
if (cos_index == 0) { // cos
s_solution_indices[1] = 1; // c (adjacent)
s_solution_indices[2] = 0; // a (hypotenuse)
} else { // sec
s_solution_indices[1] = 0; // a (hypotenuse)
s_solution_indices[2] = 1; // c (adjacent)
}
} else {
s_solution_indices[0] = 0; // sin/csc
if (sin_index == 0) { // sin
s_solution_indices[1] = 1; // c (opposite)
s_solution_indices[2] = 0; // a (hypotenuse)
} else { // csc
s_solution_indices[1] = 0; // a (hypotenuse)
s_solution_indices[2] = 1; // c (opposite)
}
}
break;
case 2:
snprintf(side_a_buffer, sizeof(side_a_buffer), "%u", s_triangle_layer->side_a);
snprintf(side_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b);
s_options_over[0] = side_a_buffer;
s_options_over[1] = side_b_buffer;
s_options_under[0] = side_a_buffer;
s_options_under[1] = side_b_buffer;
// store solution
if (s_triangle_layer->theta_angle_is_left) {
s_solution_indices[0] = 0; // sin/csc
if (sin_index == 0) { // sin
s_solution_indices[1] = 1; // b (opposite)
s_solution_indices[2] = 0; // a (hypotenuse)
} else { // csc
s_solution_indices[1] = 0; // a (hypotenuse)
s_solution_indices[2] = 1; // b (opposite)
}
} else {
s_solution_indices[0] = 1;
if (cos_index == 0) { // cos
s_solution_indices[1] = 1; // b (adjacent)
s_solution_indices[2] = 0; // a (hypotenuse)
} else { // sec
s_solution_indices[1] = 0; // a (hypotenuse)
s_solution_indices[2] = 1; // b (adjacent)
}
}
}
text_layer_set_text(s_side_a_txt_layer, side_a_buffer);
text_layer_set_text(s_side_b_txt_layer, side_b_buffer);
text_layer_set_text(s_side_c_txt_layer, side_c_buffer);
// drop menus
//// determine trigonometric function options
s_sin = s_sin_arr[sin_index];
s_cos = s_cos_arr[cos_index];
s_tan = s_tan_arr[tan_index];
//// set up menu options
s_options_function[0] = s_sin;
s_options_function[1] = s_cos;
s_options_function[2] = s_tan;
//// create drop window
// drop window
s_drop_window = window_create();
window_set_window_handlers(
s_drop_window,
(WindowHandlers){.load = drop_menu_window_load, .unload = drop_menu_window_unload});
// setup vibes
s_vibe_segments[0] = (uint32_t)(s_triangle_layer->angle_A * 2);
s_vibe_segments[1] = 200;
s_vibe_segments[2] = (uint32_t)(s_triangle_layer->angle_B * 2);
s_vibe_segments[3] = 200;
s_vibe_segments[4] = (uint32_t)(s_triangle_layer->angle_C * 2);
s_vibe_pattern = (VibePattern){
.durations = s_vibe_segments,
.num_segments = ARRAY_LENGTH(s_vibe_segments),
};
// countdown
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);
s_alarm_zap_repeat_countdown = 0;
if (s_alarm_reason == WAKE_AWARENESS_ALARM || s_alarm_reason == WAKE_RECOVERY_AWARENESS_ALARM) {
s_awareness_countdown_int = settings.AwarenessTimeLimitSeconds;
text_layer_set_font(s_countdown_txt_layer, s_font_lecoish_small);
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));
schedule_alarm(WAKE_RECOVERY_AWARENESS_ALARM);
// configure UI around current triangle in s_triangle_layer
update_for_new_triangle();
// schedule recovery alarm and begin countdown :)
schedule_alarm(WAKE_RECOVERY_ALARM);
tick_timer_service_subscribe(SECOND_UNIT, awareness_alarm_seconds_handler);
} else {
s_awareness_countdown_int = 0;
s_main_countdown_till_zap = settings.PavlokMainDelay;
text_layer_set_font(s_countdown_txt_layer, s_font_mansalva_small);
text_layer_set_text(s_countdown_txt_layer, "🏁 hold select 🏁");
layer_add_child(window_layer, text_layer_get_layer(s_countdown_txt_layer));
schedule_alarm(WAKE_RECOVERY_MAIN_ALARM);
tick_timer_service_subscribe(SECOND_UNIT, main_alarm_seconds_handler);
}
}
static void alarm_window_unload() {
vibes_cancel();
tick_timer_service_unsubscribe();
text_layer_destroy(s_countdown_txt_layer);
window_destroy(s_drop_window);
@@ -816,21 +740,8 @@ static void alarm_window_unload() {
}
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;
if (s_alarm_reason == WAKE_RECOVERY_ALARM) {
send_zap(settings.PavlokIntensity);
}
window_set_window_handlers(
@@ -931,12 +842,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(settings.PavlokMax);
vibes_double_pulse();
return; // early return to avoid overwriting recovery/awareness alarm
}
bool success = schedule_alarm(WAKE_MAIN_ALARM);
bool success = schedule_alarm(WAKE_ALARM);
if (!success) {
return; // early return to avoid indicating alarm was set when it was not
}
@@ -1053,8 +959,6 @@ 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});
@@ -1072,7 +976,7 @@ static void init() {
// initialize pkjs
app_message_register_inbox_received(inbox_received_handler);
app_message_open(128, 128); // TODO tighten sizes
app_message_open(128, 64);
// load settings from persist (or set defaults if not present)
if (persist_exists(STORAGE_KEY_SETTINGS)) {
@@ -1080,21 +984,24 @@ static void init() {
} else {
settings.TutorialEnabled = true;
settings.PavlokEnabled = false;
settings.PavlokIntervalSeconds = 10;
settings.PavlokMin = 30;
settings.PavlokMax = 60;
settings.PavlokStep = 10;
settings.PavlokMainDelay = 60;
settings.AwarenessTimeGapMinutes = 9;
settings.AwarenessTimeLimitSeconds = 30;
settings.PavlokIntensity = 50;
settings.TriangleCount = 5;
settings.TriangeMinSecs = 25;
}
// read even when opening setter to ensure a recovery alarm is not able to be overwritten
s_alarm_reason = persist_read_int(STORAGE_KEY_ALARM_REASON);
switch (launch_reason()) {
case APP_LAUNCH_WAKEUP:
push_alarm_window();
break;
default:
if (s_alarm_reason == WAKE_RECOVERY_ALARM) {
push_alarm_window();
} else {
push_setter_window();
}
};
}
-1
View File
@@ -2,7 +2,6 @@
typedef enum
{
STATUS_MAIN_TUTORIAL = 0,
STATUS_CONFIRMATION = 1,
STATUS_SET_FAILURE = 2,
STATUS_MATH_FAILURE = 3
+2 -4
View File
@@ -3,10 +3,8 @@
typedef enum
{
WAKE_MAIN_ALARM = 0,
WAKE_AWARENESS_ALARM = 1,
WAKE_RECOVERY_MAIN_ALARM = 2,
WAKE_RECOVERY_AWARENESS_ALARM = 3
WAKE_ALARM = 0,
WAKE_RECOVERY_ALARM = 1,
} WakeupReason;
typedef struct TheTime {
+5 -12
View File
@@ -2,24 +2,17 @@
#include <stdint.h>
// BUMP ON UPDATES
#define STORAGE_KEY_SETTINGS 50
#define STORAGE_KEY_ALARM_REASON 100
#define STORAGE_KEY_SETTINGS 51 // BUMP ON MAJOR UPDATES
#define STORAGE_KEY_ALARM_HOUR 0
#define STORAGE_KEY_ALARM_MINUTE 1
#define STORAGE_KEY_PAVLOK_INTENSITY 2
#define STORAGE_KEY_ALARM_REASON 2
typedef struct AppSettings {
bool TutorialEnabled;
bool PavlokEnabled;
uint8_t PavlokMin;
uint8_t PavlokMax;
uint8_t PavlokStep;
uint8_t PavlokMainDelay;
uint8_t PavlokIntervalSeconds;
uint8_t AwarenessTimeGapMinutes;
uint8_t AwarenessTimeLimitSeconds;
uint8_t PavlokIntensity;
uint8_t TriangleCount;
uint8_t TriangeMinSecs;
} __attribute__((__packed__)) AppSettings;
_Static_assert(sizeof(AppSettings) <= 256, "AppSettings exceeds Pebble 256-byte persist limit!");
+23 -9
View File
@@ -33,17 +33,11 @@ static void right_triangle_layer_update_proc(Layer *layer, GContext *ctx) {
// circle theta angle
graphics_context_set_stroke_color(ctx, GColorRed);
graphics_draw_circle(ctx, GPoint(tri->theta_angle_is_left ? start_x : end_x, base_y), 4);
graphics_context_set_stroke_width(ctx, 2);
graphics_draw_circle(ctx, GPoint(tri->theta_angle_is_left ? start_x : end_x, base_y), 5);
}
RightTriangleLayer *right_triangle_layer_create(GRect frame, GColor color) {
RightTriangleLayer *tri = malloc(sizeof(RightTriangleLayer));
if (!tri)
return NULL;
// set color
tri->color = color;
static void right_triangle_layer_generate_properties(RightTriangleLayer *tri, GRect frame) {
// determine angles
tri->angle_A = 90; // right angle at apex
tri->theta_angle_is_left = rand() % 2;
@@ -65,6 +59,18 @@ RightTriangleLayer *right_triangle_layer_create(GRect frame, GColor color) {
tri->side_b = (uint16_t)(((uint32_t)sin_B * tri->side_a) / TRIG_MAX_RATIO);
tri->side_c = (uint16_t)(((uint32_t)sin_C * tri->side_a) / TRIG_MAX_RATIO);
} while (tri->side_a == tri->side_b || tri->side_a == tri->side_c || tri->side_b == tri->side_c);
}
RightTriangleLayer *right_triangle_layer_create(GRect frame, GColor color) {
RightTriangleLayer *tri = malloc(sizeof(RightTriangleLayer));
if (!tri)
return NULL;
// set color
tri->color = color;
// generate this triangle's angles and side lengths
right_triangle_layer_generate_properties(tri, frame);
// create layer with room for a pointer-sized data slot & store the RightTriangleLayer* there
tri->layer = layer_create_with_data(frame, sizeof(RightTriangleLayer *));
@@ -74,6 +80,14 @@ RightTriangleLayer *right_triangle_layer_create(GRect frame, GColor color) {
return tri;
}
void right_triangle_layer_regen(RightTriangleLayer *tri) {
if (!tri || !tri->layer)
return;
right_triangle_layer_generate_properties(tri, layer_get_bounds(tri->layer));
layer_mark_dirty(tri->layer);
}
void right_triangle_layer_destroy(RightTriangleLayer *tri) {
if (!tri)
return;
+1
View File
@@ -20,4 +20,5 @@ typedef struct {
} __attribute__((__packed__)) RightTriangleLayer;
RightTriangleLayer *right_triangle_layer_create(GRect frame, GColor color);
void right_triangle_layer_regen(RightTriangleLayer *tri);
void right_triangle_layer_destroy(RightTriangleLayer *tri);
+18 -63
View File
@@ -32,59 +32,14 @@ module.exports = [
"defaultValue": "Test API Key w/Beep"
},
{
"id": "pavlok_zap_min",
"id": "pavlok_zap_intensity",
"type": "slider",
"messageKey": "PKJS_PAVLOK_ZAP_MIN",
"defaultValue": 30,
"messageKey": "PKJS_PAVLOK_ZAP_INTENSITY",
"defaultValue": 50,
"min": 5,
"max": 100,
"step": 5,
"label": "Zap Intensity (Base)",
"description": "% intensity of base zap during alarms."
},
{
"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 max zap; only takes effect if greater than base. Used for force exit/alarm overwrite penalties, as well as max value to step to in awareness alarms if zap step is enabled."
},
{
"id": "pavlok_zap_step",
"type": "slider",
"messageKey": "PKJS_PAVLOK_ZAP_STEP",
"defaultValue": 10,
"min": 0,
"max": 100,
"step": 5,
"label": "Zap Step",
"description": "% to increment base zap intensity after each failed awareness alarm; set to 0 to disable."
},
{
"id": "pavlok_zap_main_delay",
"type": "slider",
"messageKey": "PKJS_PAVLOK_ZAP_MAIN_DELAY",
"defaultValue": 60,
"min": 15,
"max": 120,
"step": 1,
"label": "Zap Main Alarm Delay",
"description": "Seconds into main alarm to start zaps."
},
{
"id": "pavlok_zap_interval",
"type": "slider",
"messageKey": "PKJS_PAVLOK_ZAP_INTERVAL",
"defaultValue": 10,
"min": 1,
"max": 60,
"step": 1,
"label": "Zap Repeat Interval",
"description": "Seconds between zaps during alarms."
"label": "Zap Intensity %"
}
]
},
@@ -94,27 +49,27 @@ module.exports = [
{
"type": "heading",
"size": 3,
"defaultValue": "Awareness Alarm"
"defaultValue": "Alarm Options"
},
{
"id": "awareness_time_gap",
"id": "triangle_count",
"type": "slider",
"messageKey": "PKJS_AWARENESS_TIME_GAP",
"defaultValue": 9,
"messageKey": "PKJS_TRIANGLE_COUNT",
"defaultValue": 5,
"min": 1,
"max": 30,
"label": "Gap (Minutes)",
"description": "Time gap between main alarm and subsequent awareness alarms."
"max": 20,
"label": "Triangle Count",
"description": "Number of triangles to solve before disarming the alarm."
},
{
"id": "awareness_time_limit",
"id": "triangle_min_secs",
"type": "slider",
"messageKey": "PKJS_AWARENESS_TIME_LIMIT",
"defaultValue": 30,
"min": 3,
"max": 120,
"label": "Time Limit (Seconds)",
"description": "Time limit the awareness alarm must be solved within to disarm Trigalarm."
"messageKey": "PKJS_TRIANGLE_MIN_SECS",
"defaultValue": 25,
"min": 5,
"max": 60,
"label": "Minimum Time Limit (Seconds)",
"description": "Time limit the last/fastest triangle must be solved within. All other time limits are calculated based on this one."
}
]
},
+2 -10
View File
@@ -8,19 +8,11 @@ module.exports = function (minified) {
if (this.get()) {
clayConfig.getItemById('pavlok_api_key').show();
clayConfig.getItemById('pavlok_test_beep').show();
clayConfig.getItemById('pavlok_zap_min').show();
clayConfig.getItemById('pavlok_zap_max').show();
clayConfig.getItemById('pavlok_zap_step').show();
clayConfig.getItemById('pavlok_zap_main_delay').show();
clayConfig.getItemById('pavlok_zap_interval').show();
clayConfig.getItemById('pavlok_zap_intensity').show();
} else {
clayConfig.getItemById('pavlok_api_key').hide();
clayConfig.getItemById('pavlok_test_beep').hide();
clayConfig.getItemById('pavlok_zap_min').hide();
clayConfig.getItemById('pavlok_zap_max').hide();
clayConfig.getItemById('pavlok_zap_step').hide();
clayConfig.getItemById('pavlok_zap_main_delay').hide();
clayConfig.getItemById('pavlok_zap_interval').hide();
clayConfig.getItemById('pavlok_zap_intensity').hide();
}
}
+1 -3
View File
@@ -10,9 +10,7 @@ Pebble.addEventListener('showConfiguration', function () {
// custom listener so messages can be sent from customClay.js
Pebble.addEventListener('webviewclosed', function(e) {
if (e && !e.response) {
return;
}
if (!e || !e.response) { return; }
var data;
try {