diff options
author | TheOnePerson <a.nebinger@web.de> | 2023-03-11 22:26:36 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-11 16:26:36 -0500 |
commit | 2d46a9bf9e9952b691b7ef57640038655b4e2bba (patch) | |
tree | 9beb375cdb7c17f430ff593d8cbd77305d09933a | |
parent | 7584f9bf98ea0d42a3c02e92d24f541a54836d8b (diff) | |
download | Sensor-Watch-2d46a9bf9e9952b691b7ef57640038655b4e2bba.tar.gz Sensor-Watch-2d46a9bf9e9952b691b7ef57640038655b4e2bba.tar.bz2 Sensor-Watch-2d46a9bf9e9952b691b7ef57640038655b4e2bba.zip |
Timer Face: Advanced countdown face with presets (#224)
* timer face: initial commit, fully functional
* timer face: show slot number in normal mode
---------
Co-authored-by: joeycastillo <joeycastillo@utexas.edu>
-rw-r--r-- | movement/make/Makefile | 1 | ||||
-rw-r--r-- | movement/movement_faces.h | 1 | ||||
-rw-r--r-- | movement/watch_faces/complication/timer_face.c | 366 | ||||
-rw-r--r-- | movement/watch_faces/complication/timer_face.h | 103 |
4 files changed, 471 insertions, 0 deletions
diff --git a/movement/make/Makefile b/movement/make/Makefile index 3ce60dc2..bfdd1a23 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -99,6 +99,7 @@ SRCS += \ ../watch_faces/complication/discgolf_face.c \ ../watch_faces/complication/habit_face.c \ ../watch_faces/clock/repetition_minute_face.c \ + ../watch_faces/complication/timer_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 6ae0ec0f..a335c6e5 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -77,6 +77,7 @@ #include "discgolf_face.h" #include "habit_face.h" #include "repetition_minute_face.h" +#include "timer_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/timer_face.c b/movement/watch_faces/complication/timer_face.c new file mode 100644 index 00000000..4b0b70b1 --- /dev/null +++ b/movement/watch_faces/complication/timer_face.c @@ -0,0 +1,366 @@ +/* + * MIT License + * + * Copyright (c) 2022 Andreas Nebinger, building on Wesley Ellis’ countdown_face.c + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +//----------------------------------------------------------------------------- + +#include <stdlib.h> +#include <string.h> +#include "timer_face.h" +#include "watch.h" +#include "watch_utility.h" + +static const uint16_t _default_timer_values[] = {0x200, 0x500, 0xA00, 0x1400, 0x2D02}; // default timers: 2 min, 5 min, 10 min, 20 min, 2 h 45 min + +// sound sequence for a single beeping sequence +static const int8_t _sound_seq_beep[] = {BUZZER_NOTE_C8, 3, BUZZER_NOTE_REST, 3, -2, 2, BUZZER_NOTE_C8, 5, BUZZER_NOTE_REST, 25, 0}; +static const int8_t _sound_seq_start[] = {BUZZER_NOTE_C8, 2, 0}; + +static uint8_t _beeps_to_play; // temporary counter for ring signals playing + +static inline int32_t _get_tz_offset(movement_settings_t *settings) { + return movement_timezone_offsets[settings->bit.time_zone] * 60; +} + +static void _signal_callback() { + if (_beeps_to_play) { + _beeps_to_play--; + watch_buzzer_play_sequence((int8_t *)_sound_seq_beep, _signal_callback); + } +} + +static void _start(timer_state_t *state, movement_settings_t *settings, bool with_beep) { + if (state->timers[state->current_timer].value == 0) return; + watch_date_time now = watch_rtc_get_date_time(); + state->now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); + if (state->mode == pausing) + state->target_ts = state->now_ts + state->paused_left; + else + state->target_ts = watch_utility_offset_timestamp(state->now_ts, + state->timers[state->current_timer].unit.hours, + state->timers[state->current_timer].unit.minutes, + state->timers[state->current_timer].unit.seconds); + watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, _get_tz_offset(settings)); + state->mode = running; + movement_schedule_background_task(target_dt); + watch_set_indicator(WATCH_INDICATOR_BELL); + if (settings->bit.button_should_sound && with_beep) watch_buzzer_play_sequence((int8_t *)_sound_seq_start, NULL); +} + +static void _draw(timer_state_t *state, uint8_t subsecond) { + char buf[14]; + uint32_t delta; + div_t result; + uint8_t h, min, sec; + + switch (state->mode) { + case pausing: + if (state->pausing_seconds % 2) + watch_clear_indicator(WATCH_INDICATOR_BELL); + else + watch_set_indicator(WATCH_INDICATOR_BELL); + if (state->pausing_seconds != 1) + // not 1st iteration (or 256th): do not write anything + return; + // fall through + case running: + delta = state->target_ts - state->now_ts; + result = div(delta, 60); + sec = result.rem; + result = div(result.quot, 60); + min = result.rem; + h = result.quot; + sprintf(buf, " %02u%02u%02u", h, min, sec); + break; + case setting: + if (state->settings_state == 1) { + // ask it the current timer shall be erased + sprintf(buf, " CLEAR%c", state->erase_timer_flag ? 'y' : 'n'); + watch_clear_colon(); + } else if (state->settings_state == 5) { + sprintf(buf, " LOOP%c", state->timers[state->current_timer].unit.repeat ? 'y' : 'n'); + watch_clear_colon(); + } else { + sprintf(buf, " %02u%02u%02u", state->timers[state->current_timer].unit.hours, + state->timers[state->current_timer].unit.minutes, + state->timers[state->current_timer].unit.seconds); + watch_set_colon(); + } + break; + case waiting: + sprintf(buf, " %02u%02u%02u", state->timers[state->current_timer].unit.hours, + state->timers[state->current_timer].unit.minutes, + state->timers[state->current_timer].unit.seconds); + break; + } + buf[0] = 49 + state->current_timer; + if (state->mode == setting && subsecond % 2) { + // blink the current settings value + if (state->settings_state == 0) buf[0] = ' '; + else if (state->settings_state == 1 || state->settings_state == 5) buf[6] = ' '; + else buf[(state->settings_state - 1) * 2 - 1] = buf[(state->settings_state - 1) * 2] = ' '; + } + watch_display_string(buf, 3); + // set lap indicator when we have a looping timer + if (state->timers[state->current_timer].unit.repeat) watch_set_indicator(WATCH_INDICATOR_LAP); + else watch_clear_indicator(WATCH_INDICATOR_LAP); +} + +static void _reset(timer_state_t *state) { + state->mode = waiting; + movement_cancel_background_task(); + watch_clear_indicator(WATCH_INDICATOR_BELL); +} + +static void _set_next_valid_timer(timer_state_t *state) { + if ((state->timers[state->current_timer].value & 0xFFFFFF) == 0) { + uint8_t i = state->current_timer; + do { + i = (i + 1) % TIMER_SLOTS; + } while ((state->timers[i].value & 0xFFFFFF) == 0 && i != state->current_timer); + state->current_timer = i; + } +} + +static void _resume_setting(timer_state_t *state) { + state->settings_state = 0; + state->mode = waiting; + movement_request_tick_frequency(1); + _set_next_valid_timer(state); +} + +static void _settings_increment(timer_state_t *state) { + switch(state->settings_state) { + case 0: + state->current_timer = (state->current_timer + 1) % TIMER_SLOTS; + break; + case 1: + state->erase_timer_flag ^= 1; + break; + case 2: + state->timers[state->current_timer].unit.hours = (state->timers[state->current_timer].unit.hours + 1) % 24; + break; + case 3: + state->timers[state->current_timer].unit.minutes = (state->timers[state->current_timer].unit.minutes + 1) % 60; + break; + case 4: + state->timers[state->current_timer].unit.seconds = (state->timers[state->current_timer].unit.seconds + 1) % 60; + break; + case 5: + state->timers[state->current_timer].unit.repeat ^= 1; + break; + default: + // should never happen + break; + } + return; +} + +static void _abort_quick_cycle(timer_state_t *state) { + if (state->quick_cycle) { + state->quick_cycle = false; + movement_request_tick_frequency(4); + } +} + +static inline bool _check_for_signal() { + if (_beeps_to_play) { + _beeps_to_play = 0; + return true; + } + return false; +} + +void timer_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(timer_state_t)); + timer_state_t *state = (timer_state_t *)*context_ptr; + memset(*context_ptr, 0, sizeof(timer_state_t)); + for (uint8_t i = 0; i < sizeof(_default_timer_values) / sizeof(uint16_t); i++) { + state->timers[i].value = _default_timer_values[i]; + } + } +} + +void timer_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + timer_state_t *state = (timer_state_t *)context; + watch_display_string("TR", 0); + watch_set_colon(); + if(state->mode == running) { + watch_date_time now = watch_rtc_get_date_time(); + state->now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); + watch_set_indicator(WATCH_INDICATOR_BELL); + } else { + state->pausing_seconds = 1; + _beeps_to_play = 0; + } +} + +bool timer_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + (void) settings; + timer_state_t *state = (timer_state_t *)context; + uint8_t subsecond = event.subsecond; + + switch (event.event_type) { + case EVENT_ACTIVATE: + _draw(state, event.subsecond); + break; + case EVENT_TICK: + if (state->mode == running) state->now_ts++; + else if (state->mode == pausing) state->pausing_seconds++; + else if (state->quick_cycle) { + if (watch_get_pin_level(BTN_ALARM)) { + _settings_increment(state); + subsecond = 0; + } else _abort_quick_cycle(state); + } + _draw(state, subsecond); + break; + case EVENT_LIGHT_BUTTON_DOWN: + switch (state->mode) { + case pausing: + case running: + movement_illuminate_led(); + break; + case setting: + if (state->erase_timer_flag) { + state->timers[state->current_timer].value = 0; + state->erase_timer_flag = false; + } + state->settings_state = (state->settings_state + 1) % 6; + if (state->settings_state == 1 && state->timers[state->current_timer].value == 0) state->settings_state = 2; + else if (state->settings_state == 5 && (state->timers[state->current_timer].value & 0xFFFFFF) == 0) state->settings_state = 0; + break; + default: + break; + } + _draw(state, event.subsecond); + break; + case EVENT_LIGHT_BUTTON_UP: + if (state->mode == waiting) movement_illuminate_led(); + break; + case EVENT_ALARM_BUTTON_UP: + _abort_quick_cycle(state); + if (_check_for_signal()) break;; + switch (state->mode) { + case running: + state->mode = pausing; + state->pausing_seconds = 0; + state->paused_left = state->target_ts - state->now_ts; + movement_cancel_background_task(); + break; + case pausing: + _start(state, settings, false); + break; + case waiting: { + uint8_t last_timer = state->current_timer; + state->current_timer = (state->current_timer + 1) % TIMER_SLOTS; + _set_next_valid_timer(state); + // start the time immediately if there is only one valid timer slot + if (last_timer == state->current_timer) _start(state, settings, true); + break; + } + case setting: + _settings_increment(state); + subsecond = 0; + break; + } + _draw(state, subsecond); + break; + case EVENT_LIGHT_LONG_PRESS: + if (state->mode == waiting) { + // initiate settings + state->mode = setting; + state->settings_state = 0; + state->erase_timer_flag = false; + movement_request_tick_frequency(4); + } else if (state->mode == setting) { + _resume_setting(state); + } + _draw(state, event.subsecond); + break; + case EVENT_BACKGROUND_TASK: + // play the alarm + _beeps_to_play = 4; + watch_buzzer_play_sequence((int8_t *)_sound_seq_beep, _signal_callback); + _reset(state); + if (state->timers[state->current_timer].unit.repeat) _start(state, settings, false); + break; + case EVENT_ALARM_LONG_PRESS: + switch(state->mode) { + case setting: + switch (state->settings_state) { + case 0: + state->current_timer = 0; + break; + case 2: + case 3: + case 4: + state->quick_cycle = true; + movement_request_tick_frequency(8); + break; + default: + break; + } + break; + case waiting: + _start(state, settings, true); + break; + case pausing: + case running: + _reset(state); + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_C7, 50); + break; + default: + break; + } + _draw(state, event.subsecond); + break; + case EVENT_ALARM_LONG_UP: + _abort_quick_cycle(state); + break; + case EVENT_MODE_LONG_PRESS: + case EVENT_TIMEOUT: + _abort_quick_cycle(state); + movement_move_to_face(0); + break; + default: + movement_default_loop_handler(event, settings); + break; + } + + return true; +} + +void timer_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + timer_state_t *state = (timer_state_t *)context; + if (state->mode == setting) { + state->settings_state = 0; + state->mode = waiting; + } +} diff --git a/movement/watch_faces/complication/timer_face.h b/movement/watch_faces/complication/timer_face.h new file mode 100644 index 00000000..293f6524 --- /dev/null +++ b/movement/watch_faces/complication/timer_face.h @@ -0,0 +1,103 @@ +/* + * MIT License + * + * Copyright (c) 2022 Andreas Nebinger, based on Wesley Ellis’ countdown face. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +//----------------------------------------------------------------------------- + +#ifndef TIMER_FACE_H_ +#define TIMER_FACE_H_ + +#include "movement.h" + +/* + * Advanced timer/countdown face with pre-set timer lengths + * + * This watch face provides the functionality of starting a countdown by choosing + * one out of nine programmable timer presets. A timer/countdown can be 23 hours, + * 59 minutes, and 59 seconds max. A timer can also be set to auto-repeat, which + * is indicated by the lap indicator. + * + * How to use in NORMAL mode: + * - Short-pressing the alarm button cycles through all pre-set timer lengths. + * Find the current timer slot number in the upper right-hand corner. + * - Long-pressing the alarm button starts the timer. + * - Long-pressing the light button initiates settings mode. + * + * How to use in SETTINGS mode: + * - There are up to nine slots for storing a timer setting. The current slot is + * indicated by the number in the upper right-hand corner. + * - Short-pressing the light button cycles through the settings values of each + * timer slot in the following order: hours - minutes - seconds - timer repeat + * - Short-pressing the alarm button alters the current settings value. + * - Long-pressing the light button resumes to normal mode. + * + */ + +#define TIMER_SLOTS 9 // offer 9 timer slots + +typedef enum { + waiting, + running, + setting, + pausing +} timer_mode_t; + +typedef union { + struct { + uint8_t hours; + uint8_t minutes; + uint8_t seconds; + bool repeat; + } unit; + uint32_t value; +} timer_setting_t; + +typedef struct { + uint32_t target_ts; + uint32_t now_ts; + uint16_t paused_left; + uint8_t pausing_seconds; + timer_setting_t timers[TIMER_SLOTS]; + uint8_t settings_state : 4; + uint8_t current_timer : 4; + uint8_t set_timers : 4; + bool erase_timer_flag : 1; + timer_mode_t mode : 3; + bool quick_cycle : 1; +} timer_state_t; + +void timer_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void timer_face_activate(movement_settings_t *settings, void *context); +bool timer_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void timer_face_resign(movement_settings_t *settings, void *context); + +#define timer_face ((const watch_face_t){ \ + timer_face_setup, \ + timer_face_activate, \ + timer_face_loop, \ + timer_face_resign, \ + NULL, \ +}) + + +#endif // TIMER_FACE_H_ |