diff options
Diffstat (limited to 'movement')
21 files changed, 698 insertions, 59 deletions
diff --git a/movement/alt_fw/standard.h b/movement/alt_fw/backer.h index 3abcf457..3abcf457 100644 --- a/movement/alt_fw/standard.h +++ b/movement/alt_fw/backer.h diff --git a/movement/alt_fw/deep_space_now.h b/movement/alt_fw/deep_space_now.h index f07fe2cb..6cb34237 100644 --- a/movement/alt_fw/deep_space_now.h +++ b/movement/alt_fw/deep_space_now.h @@ -32,15 +32,15 @@ #define MOVEMENT_CUSTOM_BOOT_COMMANDS() { \ /* Standard Time */\ - /*\ watch_store_backup_data(0x1e0c0c, 4);\ watch_store_backup_data(0x010115, 5);\ watch_store_backup_data(0x130105, 6);\ - */\ /* Daylight Saving Time */\ + /*\ watch_store_backup_data(0x1f0c0c, 4);\ watch_store_backup_data(0x020115, 5);\ watch_store_backup_data(0x110105, 6);\ + */\ watch_store_backup_data(0x0597b9, 2);\ } diff --git a/movement/alt_fw/the_stargazer.h b/movement/alt_fw/the_stargazer.h index 50a89aad..a13dc3ac 100644 --- a/movement/alt_fw/the_stargazer.h +++ b/movement/alt_fw/the_stargazer.h @@ -27,11 +27,8 @@ #include "movement_faces.h" -#define MOVEMENT_CUSTOM_BOOT_COMMANDS() { \ - movement_state.settings.bit.led_green_color = 0x0;\ - movement_state.settings.bit.led_red_color = 0xF;\ - watch_store_backup_data(movement_state.settings.reg, 0);\ -} +#define MOVEMENT_DEFAULT_RED_COLOR 0xF +#define MOVEMENT_DEFAULT_GREEN_COLOR 0x0 const watch_face_t watch_faces[] = { simple_clock_face, diff --git a/movement/make/Makefile b/movement/make/Makefile index bf9351eb..60084594 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -43,6 +43,7 @@ SRCS += \ ../watch_faces/clock/simple_clock_face.c \ ../watch_faces/clock/world_clock_face.c \ ../watch_faces/clock/beats_face.c \ + ../watch_faces/clock/weeknumber_clock_face.c \ ../watch_faces/settings/preferences_face.c \ ../watch_faces/settings/set_time_face.c \ ../watch_faces/sensor/thermistor_readout_face.c \ @@ -60,6 +61,7 @@ SRCS += \ ../watch_faces/complication/totp_face_lfs.c \ ../watch_faces/complication/sunrise_sunset_face.c \ ../watch_faces/complication/countdown_face.c \ + ../watch_faces/complication/sailing_face.c \ ../watch_faces/complication/counter_face.c \ ../watch_faces/complication/blinky_face.c \ ../watch_faces/complication/moon_phase_face.c \ diff --git a/movement/make/make_alternate_fw.sh b/movement/make/make_alternate_fw.sh index 575c9e52..d1ce7673 100755 --- a/movement/make/make_alternate_fw.sh +++ b/movement/make/make_alternate_fw.sh @@ -3,7 +3,7 @@ fw_dir="firmware/download" sim_dir="firmware/simulate" colors=("green" "blue") -variants=("standard" "alt_time" "deep_space_now" "focus" "the_athlete" "the_backpacker" "the_stargazer") +variants=("standard" "backer" "alt_time" "deep_space_now" "focus" "the_athlete" "the_backpacker" "the_stargazer") if [ -d "$fw_dir" ] ; then rm -r "$fw_dir" @@ -25,12 +25,12 @@ do make LED=$COLOR FIRMWARE=$VARIANT mv "build/watch.uf2" "$fw_dir/$variant-$color.uf2" done - make clean + rm -rf ./build-sim emmake make FIRMWARE=$VARIANT mkdir "$sim_dir/$variant/" - mv "build/watch.wasm" "$sim_dir/$variant/" - mv "build/watch.js" "$sim_dir/$variant/" - mv "build/watch.html" "$sim_dir/$variant/index.html" + mv "build-sim/watch.wasm" "$sim_dir/$variant/" + mv "build-sim/watch.js" "$sim_dir/$variant/" + mv "build-sim/watch.html" "$sim_dir/$variant/index.html" done echo "Done." diff --git a/movement/movement.c b/movement/movement.c index 0ded11e3..3997b4a4 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -37,7 +37,9 @@ #ifndef MOVEMENT_FIRMWARE #include "movement_config.h" #elif MOVEMENT_FIRMWARE == MOVEMENT_FIRMWARE_STANDARD -#include "alt_fw/standard.h" +#include "movement_config.h" +#elif MOVEMENT_FIRMWARE == MOVEMENT_FIRMWARE_BACKER +#include "alt_fw/backer.h" #elif MOVEMENT_FIRMWARE == MOVEMENT_FIRMWARE_ALT_TIME #include "alt_fw/alt_time.h" #elif MOVEMENT_FIRMWARE == MOVEMENT_FIRMWARE_FOCUS @@ -57,6 +59,14 @@ #define MOVEMENT_SECONDARY_FACE_INDEX 0 #endif +// Set default LED colors if not set +#ifndef MOVEMENT_DEFAULT_RED_COLOR +#define MOVEMENT_DEFAULT_RED_COLOR 0x0 +#endif +#ifndef MOVEMENT_DEFAULT_GREEN_COLOR +#define MOVEMENT_DEFAULT_GREEN_COLOR 0xF +#endif + #if __EMSCRIPTEN__ #include <emscripten.h> #endif @@ -284,7 +294,8 @@ uint8_t movement_claim_backup_register(void) { void app_init(void) { memset(&movement_state, 0, sizeof(movement_state)); - movement_state.settings.bit.led_green_color = 0xF; + movement_state.settings.bit.led_red_color = MOVEMENT_DEFAULT_RED_COLOR; + movement_state.settings.bit.led_green_color = MOVEMENT_DEFAULT_GREEN_COLOR; movement_state.settings.bit.button_should_sound = true; movement_state.settings.bit.le_interval = 1; movement_state.settings.bit.led_duration = 1; diff --git a/movement/movement_config.h b/movement/movement_config.h index 19a501ea..9e446d4d 100644 --- a/movement/movement_config.h +++ b/movement/movement_config.h @@ -32,7 +32,7 @@ const watch_face_t watch_faces[] = { world_clock_face, sunrise_sunset_face, moon_phase_face, - thermistor_readout_face, + stopwatch_face, preferences_face, set_time_face, }; diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 282be0e6..9333401b 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -45,6 +45,7 @@ #include "hello_there_face.h" #include "sunrise_sunset_face.h" #include "countdown_face.h" +#include "sailing_face.h" #include "counter_face.h" #include "blinky_face.h" #include "moon_phase_face.h" @@ -58,6 +59,7 @@ #include "frequency_correction_face.h" #include "alarm_face.h" #include "ratemeter_face.h" +#include "weeknumber_clock_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index 7721b12a..fd1d167c 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -140,17 +140,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting case EVENT_BACKGROUND_TASK: // uncomment this line to snap back to the clock face when the hour signal sounds: // movement_move_to_face(state->watch_face_index); - if (watch_is_buzzer_or_led_enabled()) { - // if we are in the foreground, we can just beep. - movement_play_signal(); - } else { - // if we were in the background, we need to enable the buzzer peripheral first, - watch_enable_buzzer(); - // beep quickly (this call blocks for 275 ms), - movement_play_signal(); - // and then turn the buzzer peripheral off again. - watch_disable_buzzer(); - } + movement_play_signal(); break; default: break; diff --git a/movement/watch_faces/clock/weeknumber_clock_face.c b/movement/watch_faces/clock/weeknumber_clock_face.c new file mode 100644 index 00000000..deaaedbd --- /dev/null +++ b/movement/watch_faces/clock/weeknumber_clock_face.c @@ -0,0 +1,167 @@ +/* + * MIT License + * + * Copyright (c) 2022 Joey Castillo + * + * 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 "weeknumber_clock_face.h" +#include "watch.h" +#include "watch_utility.h" + +static void _update_alarm_indicator(bool settings_alarm_enabled, weeknumber_clock_state_t *state) { + state->alarm_enabled = settings_alarm_enabled; + if (state->alarm_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL); + else watch_clear_indicator(WATCH_INDICATOR_SIGNAL); +} + +void weeknumber_clock_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(weeknumber_clock_state_t)); + weeknumber_clock_state_t *state = (weeknumber_clock_state_t *)*context_ptr; + state->signal_enabled = false; + state->watch_face_index = watch_face_index; + } +} + +void weeknumber_clock_face_activate(movement_settings_t *settings, void *context) { + weeknumber_clock_state_t *state = (weeknumber_clock_state_t *)context; + + if (watch_tick_animation_is_running()) watch_stop_tick_animation(); + + if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); + + // handle chime indicator + if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL); + else watch_clear_indicator(WATCH_INDICATOR_BELL); + + // show alarm indicator if there is an active alarm + _update_alarm_indicator(settings->bit.alarm_enabled, state); + + watch_set_colon(); + + // this ensures that none of the timestamp fields will match, so we can re-render them all. + state->previous_date_time = 0xFFFFFFFF; +} + +bool weeknumber_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + weeknumber_clock_state_t *state = (weeknumber_clock_state_t *)context; + char buf[11]; + uint8_t pos; + + watch_date_time date_time; + uint32_t previous_date_time; + switch (event.event_type) { + case EVENT_ACTIVATE: + case EVENT_TICK: + case EVENT_LOW_ENERGY_UPDATE: + date_time = watch_rtc_get_date_time(); + previous_date_time = state->previous_date_time; + state->previous_date_time = date_time.reg; + + // check the battery voltage once a day... + if (date_time.unit.day != state->last_battery_check) { + state->last_battery_check = date_time.unit.day; + watch_enable_adc(); + uint16_t voltage = watch_get_vcc_voltage(); + watch_disable_adc(); + // 2.2 volts will happen when the battery has maybe 5-10% remaining? + // we can refine this later. + state->battery_low = (voltage < 2200); + } + + // ...and set the LAP indicator if low. + if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP); + + if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { + // everything before minutes is the same. + pos = 6; + sprintf(buf, "%02d%02d", date_time.unit.minute, watch_utility_get_weeknumber(date_time.unit.year, date_time.unit.month, date_time.unit.day)); + } else { + // other stuff changed; let's do it all. + if (!settings->bit.clock_mode_24h) { + // if we are in 12 hour mode, do some cleanup. + if (date_time.unit.hour < 12) { + watch_clear_indicator(WATCH_INDICATOR_PM); + } else { + watch_set_indicator(WATCH_INDICATOR_PM); + } + date_time.unit.hour %= 12; + if (date_time.unit.hour == 0) date_time.unit.hour = 12; + } + pos = 0; + if (event.event_type == EVENT_LOW_ENERGY_UPDATE) { + if (!watch_tick_animation_is_running()) watch_start_tick_animation(500); + sprintf(buf, "%s%2d%2d%02d ", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute); + } else { + sprintf(buf, "%s%2d%2d%02d%02d", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute, watch_utility_get_weeknumber(date_time.unit.year, date_time.unit.month, date_time.unit.day)); + } + } + watch_display_string(buf, pos); + // handle alarm indicator + if (state->alarm_enabled != settings->bit.alarm_enabled) _update_alarm_indicator(settings->bit.alarm_enabled, state); + break; + case EVENT_MODE_BUTTON_UP: + movement_move_to_next_face(); + return false; + case EVENT_LIGHT_BUTTON_DOWN: + movement_illuminate_led(); + break; + case EVENT_ALARM_LONG_PRESS: + state->signal_enabled = !state->signal_enabled; + if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL); + else watch_clear_indicator(WATCH_INDICATOR_BELL); + break; + case EVENT_BACKGROUND_TASK: + // uncomment this line to snap back to the clock face when the hour signal sounds: + // movement_move_to_face(state->watch_face_index); + if (watch_is_buzzer_or_led_enabled()) { + // if we are in the foreground, we can just beep. + movement_play_signal(); + } else { + // beep quickly (this call blocks for 275 ms), + movement_play_signal(); + } + break; + default: + break; + } + + return true; +} + +void weeknumber_clock_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +bool weeknumber_clock_face_wants_background_task(movement_settings_t *settings, void *context) { + (void) settings; + weeknumber_clock_state_t *state = (weeknumber_clock_state_t *)context; + if (!state->signal_enabled) return false; + + watch_date_time date_time = watch_rtc_get_date_time(); + + return date_time.unit.minute == 0; +} diff --git a/movement/watch_faces/clock/weeknumber_clock_face.h b/movement/watch_faces/clock/weeknumber_clock_face.h new file mode 100644 index 00000000..f0298ea8 --- /dev/null +++ b/movement/watch_faces/clock/weeknumber_clock_face.h @@ -0,0 +1,53 @@ +/* + * MIT License + * + * Copyright (c) 2022 Joey Castillo + * + * 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 WEEKNUMBER_CLOCK_FACE_H_ +#define WEEKNUMBER_CLOCK_FACE_H_ + +#include "movement.h" + +typedef struct { + uint32_t previous_date_time; + uint8_t last_battery_check; + uint8_t watch_face_index; + bool signal_enabled; + bool battery_low; + bool alarm_enabled; +} weeknumber_clock_state_t; + +void weeknumber_clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void weeknumber_clock_face_activate(movement_settings_t *settings, void *context); +bool weeknumber_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void weeknumber_clock_face_resign(movement_settings_t *settings, void *context); +bool weeknumber_clock_face_wants_background_task(movement_settings_t *settings, void *context); + +#define weeknumber_clock_face ((const watch_face_t){ \ + weeknumber_clock_face_setup, \ + weeknumber_clock_face_activate, \ + weeknumber_clock_face_loop, \ + weeknumber_clock_face_resign, \ + weeknumber_clock_face_wants_background_task, \ +}) + +#endif // SIMPLE_CLOCK_FACE_H_ diff --git a/movement/watch_faces/complication/alarm_face.c b/movement/watch_faces/complication/alarm_face.c index b1ace2c2..181a5aba 100644 --- a/movement/watch_faces/complication/alarm_face.c +++ b/movement/watch_faces/complication/alarm_face.c @@ -72,7 +72,7 @@ static const uint8_t _blink_idx2[ALARM_SETTING_STATES] = {3, 1, 5, 7, 8, 9}; static const BuzzerNote _buzzer_notes[3] = {BUZZER_NOTE_B6, BUZZER_NOTE_C8, BUZZER_NOTE_A8}; static const uint8_t _buzzer_segdata[3][2] = {{0, 3}, {1, 3}, {2, 2}}; -int8_t _wait_ticks; +static int8_t _wait_ticks; static uint8_t _get_weekday_idx(watch_date_time date_time) { date_time.unit.year += 20; @@ -103,11 +103,11 @@ static void _alarm_face_draw(movement_settings_t *settings, alarm_state_t *state if (!settings->bit.clock_mode_24h) { if (h >= 12) { watch_set_indicator(WATCH_INDICATOR_PM); - h = h % 12; - h += h ? 0 : 12; + h %= 12; } else { watch_clear_indicator(WATCH_INDICATOR_PM); } + if (h == 0) h = 12; } sprintf(buf, "%c%c%2d%2d%02d ", _dow_strings[i][0], _dow_strings[i][1], @@ -159,7 +159,8 @@ static void _alarm_resume_setting(movement_settings_t *settings, alarm_state_t * static void _alarm_update_alarm_enabled(movement_settings_t *settings, alarm_state_t *state) { // save indication for active alarms to movement settings bool active_alarms = false; - watch_date_time *now = NULL; + watch_date_time now; + bool now_init = false; uint8_t weekday_idx; uint16_t now_minutes_of_day; uint16_t alarm_minutes_of_day; @@ -170,10 +171,11 @@ static void _alarm_update_alarm_enabled(movement_settings_t *settings, alarm_sta active_alarms = true; break; } else { - if (now == NULL) { - *now = watch_rtc_get_date_time(); - weekday_idx = _get_weekday_idx(*now); - now_minutes_of_day = now->unit.hour * 60 + now->unit.minute; + if (!now_init) { + now = watch_rtc_get_date_time(); + now_init = true; + weekday_idx = _get_weekday_idx(now); + now_minutes_of_day = now.unit.hour * 60 + now.unit.minute; } alarm_minutes_of_day = state->alarm[i].hour * 60 + state->alarm[i].minute; // no more shortcuts: check days and times for all possible cases... @@ -251,7 +253,6 @@ void alarm_face_resign(movement_settings_t *settings, void *context) { state->is_setting = false; _alarm_update_alarm_enabled(settings, state); watch_set_led_off(); - watch_store_backup_data(settings->reg, 0); state->alarm_quick_ticks = false; _wait_ticks = -1; movement_request_tick_frequency(1); @@ -270,15 +271,7 @@ bool alarm_face_wants_background_task(movement_settings_t *settings, void *conte if (state->alarm[i].minute == now.unit.minute) { if (state->alarm[i].hour == now.unit.hour) { state->alarm_playing_idx = i; - if (state->alarm[i].day == ALARM_DAY_EACH_DAY) return true; - if (state->alarm[i].day == ALARM_DAY_ONE_TIME) { - // erase this alarm - state->alarm[i].day = ALARM_DAY_EACH_DAY; - state->alarm[i].minute = state->alarm[i].hour = 0; - state->alarm[i].enabled = false; - _alarm_update_alarm_enabled(settings, state); - return true; - } + if (state->alarm[i].day == ALARM_DAY_EACH_DAY || state->alarm[i].day == ALARM_DAY_ONE_TIME) return true; uint8_t weekday_idx = _get_weekday_idx(now); if (state->alarm[i].day == weekday_idx) return true; if (state->alarm[i].day == ALARM_DAY_WORKDAY && weekday_idx < 5) return true; @@ -307,7 +300,7 @@ bool alarm_face_loop(movement_event_t event, movement_settings_t *settings, void state->alarm[state->alarm_idx].minute = (state->alarm[state->alarm_idx].minute + 1) % 60; } else _abort_quick_ticks(state); } else if (!state->is_setting) { - if (_wait_ticks >= 0 && _wait_ticks <= INT8_MAX) _wait_ticks++; + if (_wait_ticks >= 0) _wait_ticks++; if (_wait_ticks == 2) { // extra long press of alarm button _wait_ticks = -1; @@ -428,16 +421,23 @@ bool alarm_face_loop(movement_event_t event, movement_settings_t *settings, void if (watch_is_buzzer_or_led_enabled()) { _alarm_play_short_beep(state->alarm[state->alarm_playing_idx].pitch); } else { - // enable, play beep and disable buzzer again - watch_enable_buzzer(); + // play beep _alarm_play_short_beep(state->alarm[state->alarm_playing_idx].pitch); - watch_disable_buzzer(); } } else { // regular alarm beeps - movement_play_alarm_beeps((state->alarm[state->alarm_idx].beeps == (ALARM_MAX_BEEP_ROUNDS - 1) ? 20 : state->alarm[state->alarm_playing_idx].beeps), + movement_play_alarm_beeps((state->alarm[state->alarm_playing_idx].beeps == (ALARM_MAX_BEEP_ROUNDS - 1) ? 20 : state->alarm[state->alarm_playing_idx].beeps), _buzzer_notes[state->alarm[state->alarm_playing_idx].pitch]); } + // one time alarm? -> erase it + if (state->alarm[state->alarm_playing_idx].day == ALARM_DAY_ONE_TIME) { + state->alarm[state->alarm_playing_idx].day = ALARM_DAY_EACH_DAY; + state->alarm[state->alarm_playing_idx].minute = state->alarm[state->alarm_playing_idx].hour = 0; + state->alarm[state->alarm_playing_idx].beeps = 5; + state->alarm[state->alarm_playing_idx].pitch = 1; + state->alarm[state->alarm_playing_idx].enabled = false; + _alarm_update_alarm_enabled(settings, state); + } break; case EVENT_MODE_BUTTON_UP: movement_move_to_next_face(); diff --git a/movement/watch_faces/complication/countdown_face.c b/movement/watch_faces/complication/countdown_face.c index b0decb01..b2206b8f 100644 --- a/movement/watch_faces/complication/countdown_face.c +++ b/movement/watch_faces/complication/countdown_face.c @@ -140,6 +140,7 @@ void countdown_face_activate(movement_settings_t *settings, void *context) { if(state->mode == cd_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); } watch_set_colon(); } diff --git a/movement/watch_faces/complication/counter_face.c b/movement/watch_faces/complication/counter_face.c index ac0388ab..fb03ce67 100644 --- a/movement/watch_faces/complication/counter_face.c +++ b/movement/watch_faces/complication/counter_face.c @@ -59,6 +59,7 @@ bool counter_face_loop(movement_event_t event, movement_settings_t *settings, vo state->counter_idx=0;//reset counter index } print_counter(state); + beep_counter(state); break; case EVENT_ALARM_LONG_PRESS: state->counter_idx=0; // reset counter index @@ -77,6 +78,27 @@ bool counter_face_loop(movement_event_t event, movement_settings_t *settings, vo return true; } +// beep counter index times +void beep_counter(counter_state_t *state) { + + int low_count = state->counter_idx/5; + int high_count = state->counter_idx - low_count * 5; + + for (int i=0; i<low_count; i++) { + watch_buzzer_play_note(BUZZER_NOTE_A6, 50); + watch_buzzer_play_note(BUZZER_NOTE_REST, 100); + } + + //sleep between high and low + watch_buzzer_play_note(BUZZER_NOTE_REST, 200); + + for (int i=0; i<high_count; i++) { + watch_buzzer_play_note(BUZZER_NOTE_B6, 50); + watch_buzzer_play_note(BUZZER_NOTE_REST, 100); + } +} + + // print counter index at the center of display. void print_counter(counter_state_t *state) { char buf[14]; diff --git a/movement/watch_faces/complication/counter_face.h b/movement/watch_faces/complication/counter_face.h index 2d389a15..430f5a8e 100644 --- a/movement/watch_faces/complication/counter_face.h +++ b/movement/watch_faces/complication/counter_face.h @@ -39,6 +39,7 @@ bool counter_face_loop(movement_event_t event, movement_settings_t *settings, vo void counter_face_resign(movement_settings_t *settings, void *context); void print_counter(counter_state_t *state); +void beep_counter(counter_state_t *state); #define counter_face ((const watch_face_t){ \ counter_face_setup, \ diff --git a/movement/watch_faces/complication/sailing_face.c b/movement/watch_faces/complication/sailing_face.c new file mode 100644 index 00000000..97868a54 --- /dev/null +++ b/movement/watch_faces/complication/sailing_face.c @@ -0,0 +1,275 @@ +/* + * MIT License + * + * Copyright (c) 2022 Wesley Ellis + * Copyright (c) 2022 Niclas Hoyer + * + * 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 "sailing_face.h" +#include "watch.h" +#include "watch_utility.h" + +#define sl_SELECTIONS 6 +#define DEFAULT_MINUTES { 5,4,1,0,0,0 } +#define UNUSED(x) (void)(x) + +static inline int32_t get_tz_offset(movement_settings_t *settings) { + return movement_timezone_offsets[settings->bit.time_zone] * 60; +} + +static void reset(sailing_state_t *state) { + state->index = 0; + state->mode = sl_waiting; + movement_cancel_background_task(); + watch_clear_indicator(WATCH_INDICATOR_BELL); +} + +static void start(sailing_state_t *state, movement_settings_t *settings) { + uint8_t minutes = state->minutes[state->index]; + if (minutes == 0) { + reset(state); + return; + } + if (state->index < 5) { + minutes -= state->minutes[state->index+1]; + } + + state->mode = sl_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)); + state->target_ts = watch_utility_offset_timestamp(state->now_ts, 0, minutes, 0); + watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, get_tz_offset(settings)); + movement_schedule_background_task_for_face(state->watch_face_index, target_dt); + watch_set_indicator(WATCH_INDICATOR_BELL); +} + +static void draw(sailing_state_t *state, uint8_t subsecond, movement_settings_t *settings) { + UNUSED(settings); + + char tmp[24]; + char buf[16]; + + uint32_t delta; + div_t result; + uint8_t min, sec; + uint8_t add = 0; + + switch (state->mode) { + case sl_running: + if (state->index < 5) { + add = state->minutes[state->index+1]; + } + if (state->now_ts >= state->target_ts) { + delta = 0; + } else { + delta = state->target_ts - state->now_ts; + } + result = div(delta, 60); + min = result.quot + add; + sec = result.rem; + + if (min > 0) { + sprintf(buf, "SL %2d%02d", min, sec); + } else { + sprintf(buf, "SL %2d ", sec); + } + break; + case sl_waiting: + sprintf(buf, "SL %2d%02d", state->minutes[0], 0); + break; + case sl_setting: + // this sprintf to a larger tmp is to guarantee that no buffer overflows + // occur here (and to squelch the corresponding compiler warning) + sprintf(tmp, "SL %1d%1d%1d%1d%1d%1d", + state->minutes[0], + state->minutes[1], + state->minutes[2], + state->minutes[3], + state->minutes[4], + state->minutes[5] + ); + memcpy(buf, tmp, sizeof(buf)); + if (subsecond % 2) { + buf[4 + state->selection] = ' '; + } + break; + } + watch_display_string(buf, 0); +} + +static void ring(sailing_state_t *state, movement_settings_t *settings) { + movement_play_signal(); + state->index += 1; + if (state->index > 5) { + reset(state); + return; + } + uint8_t next_min = state->minutes[state->index]; + if (next_min == 0) { + reset(state); + return; + } + movement_cancel_background_task(); + start(state, settings); +} + +static void settings_increment(sailing_state_t *state) { + state->minutes[state->selection] += 1; + uint8_t max = 10; + if (state->selection > 0) { + max = state->minutes[state->selection-1]; + } + if (state->minutes[state->selection] >= max) { + state->minutes[state->selection] = 0; + } + // ensure that minutes are decreasing + if (state->selection < 5) { + for (uint8_t i = 0; i < 5; i++) { + if (state->minutes[i+1] >= state->minutes[i]) { + if (state->minutes[i] > 0) { + state->minutes[i+1] = state->minutes[i] - 1; + } else { + state->minutes[i+1] = 0; + } + } + } + } + return; +} + +void sailing_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(sailing_state_t)); + sailing_state_t *state = (sailing_state_t *)*context_ptr; + memset(*context_ptr, 0, sizeof(sailing_state_t)); + static const uint8_t default_minutes[6] = DEFAULT_MINUTES; + memcpy(&state->minutes, default_minutes, sizeof(default_minutes)); + state->watch_face_index = watch_face_index; + } +} + +void sailing_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + sailing_state_t *state = (sailing_state_t *)context; + if(state->mode == sl_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)); + } +} + + +bool sailing_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + (void) settings; + sailing_state_t *state = (sailing_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + draw(state, event.subsecond, settings); + break; + case EVENT_TICK: + if (state->mode == sl_running) { + state->now_ts++; + } + draw(state, event.subsecond, settings); + break; + case EVENT_MODE_BUTTON_UP: + movement_move_to_next_face(); + break; + case EVENT_LIGHT_LONG_PRESS: + if (state->mode == sl_running) { + reset(state); + } + break; + case EVENT_LIGHT_BUTTON_UP: + switch(state->mode) { + case sl_running: + movement_illuminate_led(); + break; + case sl_waiting: + state->mode = sl_setting; + movement_request_tick_frequency(4); + break; + case sl_setting: + state->selection++; + if(state->selection >= sl_SELECTIONS) { + state->selection = 0; + state->mode = sl_waiting; + movement_request_tick_frequency(1); + } + break; + } + draw(state, event.subsecond, settings); + break; + case EVENT_ALARM_BUTTON_UP: + switch(state->mode) { + case sl_running: + ring(state, settings); + break; + case sl_waiting: + movement_play_signal(); + start(state, settings); + break; + case sl_setting: + settings_increment(state); + break; + } + draw(state, event.subsecond, settings); + break; + case EVENT_BACKGROUND_TASK: + ring(state, settings); + break; + case EVENT_ALARM_LONG_PRESS: + if (state->mode == sl_setting) { + static const uint8_t default_minutes[6] = DEFAULT_MINUTES; + memcpy(&state->minutes, default_minutes, sizeof(default_minutes)); + state->index = 0; + draw(state, event.subsecond, settings); + break; + } + break; + case EVENT_TIMEOUT: + if (state->mode != sl_running) { + movement_move_to_face(0); + } + break; + case EVENT_LOW_ENERGY_UPDATE: + default: + break; + } + + return true; +} + +void sailing_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + sailing_state_t *state = (sailing_state_t *)context; + if (state->mode == sl_setting) { + state->selection = 0; + state->mode = sl_waiting; + } +} diff --git a/movement/watch_faces/complication/sailing_face.h b/movement/watch_faces/complication/sailing_face.h new file mode 100644 index 00000000..31799097 --- /dev/null +++ b/movement/watch_faces/complication/sailing_face.h @@ -0,0 +1,68 @@ +/* + * MIT License + * + * Copyright (c) 2022 Wesley Ellis + * Copyright (c) 2022 Niclas Hoyer + * + * 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 SAILING_FACE_H_ +#define SAILING_FACE_H_ + +#include "movement.h" + +/* +A sailing sailing/timer face +*/ + + +typedef enum { + sl_waiting, + sl_running, + sl_setting +} sailing_mode_t; + +typedef struct { + uint8_t watch_face_index; + uint32_t target_ts; + uint32_t now_ts; + uint8_t index; + uint8_t minutes[6]; + uint8_t selection; + sailing_mode_t mode; +} sailing_state_t; + + +void sailing_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void sailing_face_activate(movement_settings_t *settings, void *context); +bool sailing_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void sailing_face_resign(movement_settings_t *settings, void *context); + +#define sailing_face ((const watch_face_t){ \ + sailing_face_setup, \ + sailing_face_activate, \ + sailing_face_loop, \ + sailing_face_resign, \ + NULL, \ +}) + +#endif // sailing_FACE_H_ diff --git a/movement/watch_faces/complication/stopwatch_face.c b/movement/watch_faces/complication/stopwatch_face.c index e85bbd65..2a69e9d5 100644 --- a/movement/watch_faces/complication/stopwatch_face.c +++ b/movement/watch_faces/complication/stopwatch_face.c @@ -29,6 +29,12 @@ #include "watch.h" #include "watch_utility.h" +// distant future for background task: January 1, 2083 +// see stopwatch_face_activate for details +static const watch_date_time distant_future = { + .unit = {0, 0, 0, 1, 1, 63} +}; + void stopwatch_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { (void) settings; (void) watch_face_index; @@ -49,6 +55,7 @@ static void _stopwatch_face_update_display(stopwatch_state_t *stopwatch_state, b if (stopwatch_state->seconds_counted >= 3456000) { // display maxes out just shy of 40 days, thanks to the limit on the day digits (0-39) stopwatch_state->running = false; + movement_cancel_background_task(); watch_display_string("st39235959", 0); return; } @@ -72,12 +79,21 @@ static void _stopwatch_face_update_display(stopwatch_state_t *stopwatch_state, b void stopwatch_face_activate(movement_settings_t *settings, void *context) { (void) settings; - (void) context; if (watch_tick_animation_is_running()) watch_stop_tick_animation(); + + stopwatch_state_t *stopwatch_state = (stopwatch_state_t *)context; + if (stopwatch_state->running) { + // because the low power update happens on the minute mark, and the wearer could start + // the stopwatch anytime, the low power update could fire up to 59 seconds later than + // we need it to, causing the stopwatch to display stale data. + // So let's schedule a background task that will never fire. This will keep the watch + // from entering low energy mode while the stopwatch is on screen. This background task + // will remain scheduled until the stopwatch stops OR this watch face resigns. + movement_schedule_background_task(distant_future); + } } bool stopwatch_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { - (void) settings; stopwatch_state_t *stopwatch_state = (stopwatch_state_t *)context; switch (event.event_type) { @@ -103,6 +119,9 @@ bool stopwatch_face_loop(movement_event_t event, movement_settings_t *settings, } break; case EVENT_ALARM_BUTTON_DOWN: + if (settings->bit.button_should_sound) { + watch_buzzer_play_note(BUZZER_NOTE_C8, 50); + } stopwatch_state->running = !stopwatch_state->running; if (stopwatch_state->running) { // we're running now, so we need to set the start_time. @@ -118,15 +137,28 @@ bool stopwatch_face_loop(movement_event_t event, movement_settings_t *settings, // and resume from the "virtual" start time that's that many seconds ago. stopwatch_state->start_time = watch_utility_date_time_from_unix_time(timestamp, 0); } + // schedule our keepalive task when running... + movement_schedule_background_task(distant_future); + } else { + // and cancel it when stopped. + movement_cancel_background_task(); } break; case EVENT_TIMEOUT: // explicitly ignore the timeout event so we stay on screen break; case EVENT_LOW_ENERGY_UPDATE: - if (!watch_tick_animation_is_running()) watch_start_tick_animation(500); - _stopwatch_face_update_display(stopwatch_state, false); - watch_set_indicator(WATCH_INDICATOR_BELL); + if (!watch_tick_animation_is_running()) watch_start_tick_animation(1000); + if (!stopwatch_state->running) { + // since the tick animation is running, displaying the stopped time could be misleading, + // as it could imply that the stopwatch is running. instead, show a blank display to + // indicate that we are in sleep mode. + watch_display_string("st ---- ", 0); + } else { + // this OTOH shouldn't happen anymore; if we're running, we shouldn't enter low energy mode + _stopwatch_face_update_display(stopwatch_state, false); + watch_set_indicator(WATCH_INDICATOR_BELL); + } break; default: break; @@ -138,4 +170,8 @@ bool stopwatch_face_loop(movement_event_t event, movement_settings_t *settings, void stopwatch_face_resign(movement_settings_t *settings, void *context) { (void) settings; (void) context; -}
\ No newline at end of file + + // regardless of whether we're running or stopped, cancel the task + // that was keeping us awake while on screen. + movement_cancel_background_task(); +} diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index 8dea812e..7807de83 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -96,6 +96,11 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s if (seconds < 30) scratch_time.unit.minute = floor(minutes); else scratch_time.unit.minute = ceil(minutes); + if (scratch_time.unit.minute == 60) { + scratch_time.unit.minute = 0; + scratch_time.unit.hour = (scratch_time.unit.hour + 1) % 24; + } + if (date_time.reg < scratch_time.reg) _sunrise_sunset_set_expiration(state, scratch_time); if (date_time.reg < scratch_time.reg || show_next_match) { @@ -118,6 +123,11 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s if (seconds < 30) scratch_time.unit.minute = floor(minutes); else scratch_time.unit.minute = ceil(minutes); + if (scratch_time.unit.minute == 60) { + scratch_time.unit.minute = 0; + scratch_time.unit.hour = (scratch_time.unit.hour + 1) % 24; + } + if (date_time.reg < scratch_time.reg) _sunrise_sunset_set_expiration(state, scratch_time); if (date_time.reg < scratch_time.reg || show_next_match) { @@ -371,8 +381,11 @@ bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *setti } break; case EVENT_TIMEOUT: - if (state->page || state->rise_index) { - // on timeout, exit settings mode and return to the next sunrise or sunset + if (watch_get_backup_data(1) == 0) { + // if no location set, return home + movement_move_to_face(0); + } else if (state->page || state->rise_index) { + // otherwise on timeout, exit settings mode and return to the next sunrise or sunset state->page = 0; state->rise_index = 0; movement_request_tick_frequency(1); diff --git a/movement/watch_faces/complication/tomato_face.c b/movement/watch_faces/complication/tomato_face.c index 37798daf..ed5554f2 100644 --- a/movement/watch_faces/complication/tomato_face.c +++ b/movement/watch_faces/complication/tomato_face.c @@ -124,6 +124,7 @@ void tomato_face_activate(movement_settings_t *settings, void *context) { if (state->mode == tomato_run) { 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); } watch_set_colon(); } diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index af5421f1..1605f119 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -66,8 +66,8 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v date_time.unit.second = 0; break; case 3: // year - // only allow 2021-2030. fix this sometime next decade - date_time.unit.year = ((date_time.unit.year % 10) + 1); + // only allow 2021-2050. fix this if we make it that far. + date_time.unit.year = ((date_time.unit.year % 30) + 1); break; case 4: // month date_time.unit.month = (date_time.unit.month % 12) + 1; |