diff options
114 files changed, 2530 insertions, 698 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b150afb1..6b4fc793 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,9 @@ on: branches-ignore: - gh-pages +env: + COLOR: BLUE + jobs: build: container: @@ -62,6 +62,7 @@ CFLAGS += -MD -MP -MT $(BUILD)/$(*F).o -MF $(BUILD)/$(@F).d LDFLAGS += -mcpu=cortex-m0plus -mthumb LDFLAGS += -Wl,--gc-sections LDFLAGS += -Wl,--script=$(TOP)/watch-library/hardware/linker/saml22j18.ld +LDFLAGS += -Wl,--print-memory-usage LIBS += -lm @@ -207,6 +208,10 @@ ifeq ($(LED), BLUE) CFLAGS += -DWATCH_IS_BLUE_BOARD endif +ifndef COLOR +$(error Set the COLOR variable to RED, BLUE, or GREEN depending on what board you have.) +endif + ifeq ($(COLOR), BLUE) CFLAGS += -DWATCH_IS_BLUE_BOARD endif diff --git a/movement/make/Makefile b/movement/make/Makefile index 625c7729..c294b068 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -118,6 +118,9 @@ SRCS += \ ../watch_faces/complication/flashlight_face.c \ ../watch_faces/clock/decimal_time_face.c \ ../watch_faces/clock/wyoscan_face.c \ + ../watch_faces/complication/couch_to_5k_face.c \ + ../watch_faces/clock/minute_repeater_decimal_face.c \ + ../watch_faces/complication/tuning_tones_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.c b/movement/movement.c index 0c6ed319..f0868416 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -294,7 +294,25 @@ void movement_request_wake() { } void movement_play_signal(void) { - watch_buzzer_play_sequence(signal_tune, NULL); + bool buzzer_enabled = watch_is_buzzer_or_led_enabled(); + if (!buzzer_enabled) { + watch_enable_buzzer(); + } + watch_buzzer_play_note(BUZZER_NOTE_C8, 75); + watch_buzzer_play_note(BUZZER_NOTE_REST, 100); + watch_buzzer_play_note(BUZZER_NOTE_C8, 100); + if (!buzzer_enabled) { + watch_disable_buzzer(); + } +} + +void movement_play_tune(void) { + if (!watch_is_buzzer_or_led_enabled()) { + watch_enable_buzzer(); + watch_buzzer_play_sequence(signal_tune, watch_disable_buzzer); + } else { + watch_buzzer_play_sequence(signal_tune, NULL); + } } void movement_play_alarm(void) { diff --git a/movement/movement.h b/movement/movement.h index 66bf6af4..5f30dfb8 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -307,6 +307,7 @@ void movement_cancel_background_task_for_face(uint8_t watch_face_index); void movement_request_wake(void); void movement_play_signal(void); +void movement_play_tune(void); void movement_play_alarm(void); void movement_play_alarm_beeps(uint8_t rounds, BuzzerNote alarm_note); diff --git a/movement/movement_faces.h b/movement/movement_faces.h index ff34c063..cb58612f 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -95,6 +95,9 @@ #include "flashlight_face.h" #include "decimal_time_face.h" #include "wyoscan_face.h" +#include "couch_to_5k_face.h" +#include "minute_repeater_decimal_face.h" +#include "tuning_tones_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/clock/beats_face.c b/movement/watch_faces/clock/beats_face.c index 50c3284d..85bcbe08 100644 --- a/movement/watch_faces/clock/beats_face.c +++ b/movement/watch_faces/clock/beats_face.c @@ -1,3 +1,27 @@ +/* + * MIT License + * + * Copyright (c) 2023 Wesley Ellis <https://github.com/tahnok> + * + * 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 "beats_face.h" diff --git a/movement/watch_faces/clock/beats_face.h b/movement/watch_faces/clock/beats_face.h index 2bbbc26d..4a066241 100644 --- a/movement/watch_faces/clock/beats_face.h +++ b/movement/watch_faces/clock/beats_face.h @@ -1,6 +1,41 @@ +/* + * MIT License + * + * Copyright (c) 2023 Wesley Ellis <https://github.com/tahnok> + * + * 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 BEATS_FACE_H_ #define BEATS_FACE_H_ +/* + * BEATS TIME face + * + * The Beat Time face displays the current Swatch Internet Time, or .beat time. + * This is a decimal time system that divides the day into 1000 beats. + * + * The three large digits in the bottom row indicate the current beat, and the + * two smaller digits (normally the seconds in Simple Clock) indicate the + * fractional beat; so for example you can read “67214” as “beat 672.14”. + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/clock/decimal_time_face.h b/movement/watch_faces/clock/decimal_time_face.h index b9f14f33..3d3a9138 100644 --- a/movement/watch_faces/clock/decimal_time_face.h +++ b/movement/watch_faces/clock/decimal_time_face.h @@ -25,10 +25,8 @@ #ifndef DECIMAL_TIME_FACE_H_ #define DECIMAL_TIME_FACE_H_ -#include "movement.h" - /* - * DECIMAL TIME FACE + * DECIMAL TIME face * * This face presents the current time as hours and hundredths of an hour. Every hundreth of an hour, or "centihour", * occurs every 36 seconds. Because they range from 0 to 99, centihours, in the seventies range, will be displayed with a lowercase 7. @@ -46,9 +44,10 @@ * https://hr.colostate.edu/minute-to-decimal-conversion-chart/ * * Many thanks go to Joey Castillo for making this project happen. - * */ +#include "movement.h" + typedef struct { bool chime_enabled; // did the user enable hourly chime for this face? uint8_t features_to_show : 2 ; // what features are to be displayed? diff --git a/movement/watch_faces/clock/mars_time_face.h b/movement/watch_faces/clock/mars_time_face.h index d34792e9..04917686 100644 --- a/movement/watch_faces/clock/mars_time_face.h +++ b/movement/watch_faces/clock/mars_time_face.h @@ -25,6 +25,32 @@ #ifndef MARS_TIME_FACE_H_ #define MARS_TIME_FACE_H_ +/* + * MARS TIME face + * + * This watch face is dedicated to Martian timekeeping. + * It has several modes, and can display either a time or a date. + * + * Pressing the ALARM button cycles through different time zones on Mars: + * MC - Mars Coordinated Time, the time at Airy-0 Crater on the Martian prime meridian + * ZH - Local mean solar time for the Zhurong rover + * PE - LMST for the Perseverance rover + * IN - LMST for the Insight lander + * CU - LMST for the Curiosity rover + * + * Press the LIGHT button to toggle between displaying time and date: + * MC S - the Mars Sol Date, Martian days since December 29, 1873 + * ZH Sol - Mission sol for the Zhurong rover + * PE Sol - Mission sol for the Perseverance rover + * IN S - Mission sol for the InSight lander + * CU S - Mission sol for the Curiosity rover + * + * Note that where the mission sol is below 1000, this watch face displays + * the word “Sol” on the bottom line. When the mission sol is over 1000, the + * word “Sol” will not fit and so it displays a stylized letter S at the top + * right. + */ + #include "movement.h" typedef enum { diff --git a/movement/watch_faces/clock/minute_repeater_decimal_face.c b/movement/watch_faces/clock/minute_repeater_decimal_face.c new file mode 100644 index 00000000..2cedc307 --- /dev/null +++ b/movement/watch_faces/clock/minute_repeater_decimal_face.c @@ -0,0 +1,238 @@ +/* + * MIT License + * + * Copyright (c) 2023 Jonas Termeau - original repetition_minute_face + * Copyright (c) 2023 Brian Blakley - modified minute_repeater_decimal_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. + */ + +/* + * This face, minute_repeater_decimal_face, is a modification of the original + * repetition_minute_face by Jonas Termeau. + * + * This version was created by BrianBinFL to use a decimal minute repeater pattern + * (hours, tens, and minutes) instead of the traditional pattern (hours, quarters, + * minutes). + * + * Also 500ms delays were added after the hours segment and after the tens segment + * to make it easier for the user to realize that the counting for the current + * segment has ended. + * + */ + +#include <stdlib.h> +#include "minute_repeater_decimal_face.h" +#include "watch.h" +#include "watch_utility.h" +#include "watch_private_display.h" + +void mrd_play_hour_chime(void) { + watch_buzzer_play_note(BUZZER_NOTE_C6, 75); + watch_buzzer_play_note(BUZZER_NOTE_REST, 500); +} + +void mrd_play_tens_chime(void) { + watch_buzzer_play_note(BUZZER_NOTE_E6, 75); + watch_buzzer_play_note(BUZZER_NOTE_REST, 150); + watch_buzzer_play_note(BUZZER_NOTE_C6, 75); + watch_buzzer_play_note(BUZZER_NOTE_REST, 750); +} + +void mrd_play_minute_chime(void) { + watch_buzzer_play_note(BUZZER_NOTE_E6, 75); + watch_buzzer_play_note(BUZZER_NOTE_REST, 500); +} + +static void _update_alarm_indicator(bool settings_alarm_enabled, minute_repeater_decimal_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 minute_repeater_decimal_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(minute_repeater_decimal_state_t)); + minute_repeater_decimal_state_t *state = (minute_repeater_decimal_state_t *)*context_ptr; + state->signal_enabled = false; + state->watch_face_index = watch_face_index; + } +} + +void minute_repeater_decimal_face_activate(movement_settings_t *settings, void *context) { + minute_repeater_decimal_state_t *state = (minute_repeater_decimal_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 minute_repeater_decimal_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + minute_repeater_decimal_state_t *state = (minute_repeater_decimal_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 >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { + // everything before seconds is the same, don't waste cycles setting those segments. + watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8); + watch_display_character_lp_seconds('0' + date_time.unit.second % 10, 9); + break; + } else 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, date_time.unit.second); + } 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, date_time.unit.second); + } + } + 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_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: + movement_play_signal(); + break; + case EVENT_LIGHT_LONG_UP: + /* + * Howdy neighbors, this is the actual complication. Like an actual + * (very expensive) watch with a repetition minute complication it's + * boring at 00:00 or 1:00 and very quite musical at 23:59 or 12:59. + */ + + date_time = watch_rtc_get_date_time(); + + + int hours = date_time.unit.hour; + int tens = date_time.unit.minute / 10; + int minutes = date_time.unit.minute % 10; + + // chiming hours + if (!settings->bit.clock_mode_24h) { + hours = date_time.unit.hour % 12; + if (hours == 0) hours = 12; + } + if (hours > 0) { + int count = 0; + for(count = hours; count > 0; --count) { + mrd_play_hour_chime(); + } + // do a little pause before proceeding to tens + watch_buzzer_play_note(BUZZER_NOTE_REST, 500); + } + + // chiming tens (if needed) + if (tens > 0) { + int count = 0; + for(count = tens; count > 0; --count) { + mrd_play_tens_chime(); + } + // do a little pause before proceeding to minutes + watch_buzzer_play_note(BUZZER_NOTE_REST, 500); + } + + // chiming minutes (if needed) + if (minutes > 0) { + int count = 0; + for(count = minutes; count > 0; --count) { + mrd_play_minute_chime(); + } + } + + break; + default: + return movement_default_loop_handler(event, settings); + } + + return true; +} + +void minute_repeater_decimal_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +bool minute_repeater_decimal_face_wants_background_task(movement_settings_t *settings, void *context) { + (void) settings; + minute_repeater_decimal_state_t *state = (minute_repeater_decimal_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/minute_repeater_decimal_face.h b/movement/watch_faces/clock/minute_repeater_decimal_face.h new file mode 100644 index 00000000..4bc9a8b6 --- /dev/null +++ b/movement/watch_faces/clock/minute_repeater_decimal_face.h @@ -0,0 +1,84 @@ +/* + * MIT License + * + * Copyright (c) 2023 Jonas Termeau - original repetition_minute_face + * Copyright (c) 2023 Brian Blakley - modified minute_repeater_decimal_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 MINUTE_REPEATER_DECIMAL_FACE_H_ +#define MINUTE_REPEATER_DECIMAL_FACE_H_ + +#include "movement.h" + +/* + * A hopefully useful complication for friendly neighbors in the dark + * + * Originating from 1676 from reverend and mechanician Edward Barlow, and + * perfected in 1820 by neighbor Abraham Breguet, a minute repeater or + * "repetition minute" is a complication in a mechanical watch or clock that + * chimes the hours and often minutes at the press of a button. There are many + * types of repeater, from the simple repeater which merely strikes the number + * of hours, to the minute repeater which chimes the time down to the minute, + * using separate tones for hours, decimal hours, and minutes. They originated + * before widespread artificial illumination, to allow the time to be determined + * in the dark, and were also used by the visually impaired. + * + * + * How to use it : + * + * Long press the light button to get an auditive reading of the time like so : + * 0..23 (1..12 if 24-hours format isn't enabled) low beep(s) for the hours + * 0..9 low-high couple pitched beeps for the tens of minutes + * 0..9 high pitched beep(s) for the remaining minutes (ones of minutes) + * + * Prerequisite : a watch with a working buzzer + * + * ~ Only in the darkness can you see the stars. - Martin Luther King ~ + * + */ + +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; +} minute_repeater_decimal_state_t; + +void mrd_play_hour_chime(void); +void mrd_play_tens_chime(void); +void mrd_play_minute_chime(void); +void minute_repeater_decimal_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void minute_repeater_decimal_face_activate(movement_settings_t *settings, void *context); +bool minute_repeater_decimal_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void minute_repeater_decimal_face_resign(movement_settings_t *settings, void *context); +bool minute_repeater_decimal_face_wants_background_task(movement_settings_t *settings, void *context); + +#define minute_repeater_decimal_face ((const watch_face_t){ \ + minute_repeater_decimal_face_setup, \ + minute_repeater_decimal_face_activate, \ + minute_repeater_decimal_face_loop, \ + minute_repeater_decimal_face_resign, \ + minute_repeater_decimal_face_wants_background_task, \ +}) + +#endif // MINUTE_REPEATER_DECIMAL_FACE_H_ diff --git a/movement/watch_faces/clock/repetition_minute_face.c b/movement/watch_faces/clock/repetition_minute_face.c index a0fbe077..fc78b2d8 100644 --- a/movement/watch_faces/clock/repetition_minute_face.c +++ b/movement/watch_faces/clock/repetition_minute_face.c @@ -151,19 +151,7 @@ bool repetition_minute_face_loop(movement_event_t event, movement_settings_t *se 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 { - // 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; case EVENT_LIGHT_LONG_UP: /* diff --git a/movement/watch_faces/clock/repetition_minute_face.h b/movement/watch_faces/clock/repetition_minute_face.h index 5a897bc1..8c3100dd 100644 --- a/movement/watch_faces/clock/repetition_minute_face.h +++ b/movement/watch_faces/clock/repetition_minute_face.h @@ -25,9 +25,9 @@ #ifndef REPETITION_MINUTE_FACE_H_ #define REPETITION_MINUTE_FACE_H_ -#include "movement.h" - /* + * REPETITION MINUTE face + * * A hopefully useful complication for friendly neighbors in the dark * * Originating from 1676 from reverend and mechanician Edward Barlow, and @@ -40,7 +40,6 @@ * before widespread artificial illumination, to allow the time to be determined * in the dark, and were also used by the visually impaired. * - * * How to use it : * * Long press the light button to get an auditive reading of the time like so : @@ -51,9 +50,10 @@ * Prerequisite : a watch with a working buzzer * * ~ Only in the darkness can you see the stars. - Martin Luther King ~ - * */ +#include "movement.h" + typedef struct { uint32_t previous_date_time; uint8_t last_battery_check; diff --git a/movement/watch_faces/clock/simple_clock_bin_led_face.c b/movement/watch_faces/clock/simple_clock_bin_led_face.c index 640f0d77..cf39c188 100644 --- a/movement/watch_faces/clock/simple_clock_bin_led_face.c +++ b/movement/watch_faces/clock/simple_clock_bin_led_face.c @@ -180,17 +180,7 @@ bool simple_clock_bin_led_face_loop(movement_event_t event, movement_settings_t 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; case EVENT_LIGHT_LONG_PRESS: if (state->flashing_state == 0) { diff --git a/movement/watch_faces/clock/simple_clock_bin_led_face.h b/movement/watch_faces/clock/simple_clock_bin_led_face.h index 918b82a4..79c7c146 100644 --- a/movement/watch_faces/clock/simple_clock_bin_led_face.h +++ b/movement/watch_faces/clock/simple_clock_bin_led_face.h @@ -25,9 +25,9 @@ #ifndef SIIMPLE_CLOCK_BIN_LED_FACE_H_ #define SIIMPLE_CLOCK_BIN_LED_FACE_H_ -#include "movement.h" - /* + * BINARY LED CLOCK FACE + * * A "fork" of the simple clock face, which provides the functionality of showing * the current time by flashing the LED using binary representation. * @@ -49,6 +49,8 @@ * represents 1. */ +#include "movement.h" + typedef struct { uint32_t previous_date_time; uint8_t last_battery_check; diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index 91400b6c..ac9a97b2 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -136,17 +136,11 @@ 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(); - } + #ifdef SIGNAL_TUNE_DEFAULT + movement_play_signal(); + #else + movement_play_tune(); + #endif break; default: return movement_default_loop_handler(event, settings); diff --git a/movement/watch_faces/clock/simple_clock_face.h b/movement/watch_faces/clock/simple_clock_face.h index 1e9babad..e74a6e86 100644 --- a/movement/watch_faces/clock/simple_clock_face.h +++ b/movement/watch_faces/clock/simple_clock_face.h @@ -25,6 +25,15 @@ #ifndef SIMPLE_CLOCK_FACE_H_ #define SIMPLE_CLOCK_FACE_H_ +/* + * SIMPLE CLOCK FACE + * + * Displays the current time, matching the original operation of the watch. + * This is the default display mode in most watch configurations. + * + * Long-press ALARM to toggle the hourly chime. + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/clock/weeknumber_clock_face.c b/movement/watch_faces/clock/weeknumber_clock_face.c index 4e40ebdc..81df5847 100644 --- a/movement/watch_faces/clock/weeknumber_clock_face.c +++ b/movement/watch_faces/clock/weeknumber_clock_face.c @@ -130,17 +130,7 @@ bool weeknumber_clock_face_loop(movement_event_t event, movement_settings_t *set 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: movement_default_loop_handler(event, settings); diff --git a/movement/watch_faces/clock/weeknumber_clock_face.h b/movement/watch_faces/clock/weeknumber_clock_face.h index f0298ea8..ec619427 100644 --- a/movement/watch_faces/clock/weeknumber_clock_face.h +++ b/movement/watch_faces/clock/weeknumber_clock_face.h @@ -25,6 +25,14 @@ #ifndef WEEKNUMBER_CLOCK_FACE_H_ #define WEEKNUMBER_CLOCK_FACE_H_ +/* + * WEEK-NUMBER WATCH FACE + * + * Same as simple clock, but has iso 8601 week number instead of seconds counter. + * + * Long-press ALARM to toggle the hourly chime. + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/clock/world_clock2_face.c b/movement/watch_faces/clock/world_clock2_face.c index 0077f639..2e1e9695 100644 --- a/movement/watch_faces/clock/world_clock2_face.c +++ b/movement/watch_faces/clock/world_clock2_face.c @@ -23,79 +23,6 @@ * SOFTWARE. */ -/* - * World Clock 2 - * ============= - * - * This is an alternative world clock face that allows the user to cycle - * through a list of selected time zones. It extends the original - * implementation by Joey Castillo. The face has two modes *display mode* - * and *settings mode*. - * - * ### Settings mode - * - * When the clock face is activated for the first time, it enters - * *settings mode*. Here, the user can select the time zones they want to - * display. The face shows a summary of the current time zone: - * - * - The top of the face displays the first two letters of the time zone - * abbreviation, such as "PS" for Pacific Standard Time or CE for - * "Central European Time". - * - * - The upper-right corner shows the index number of the time zone. This - * helps avoid confusion when multiple time zones have the same - * two-letter abbreviation. - * - * - The main display shows the offset from UTC, with a "+" indicating a - * positive offset and a "-" indicating a negative offset. For example, - * the offset for Japanese Standard Time is displayed as "+9:00". - * - * The user can navigate through the time zones and select them using the - * following buttons: - * - * - The *alarm button* moves forward to the next time zone, while the - * *light button* moves backward to the previous zone. This way, the - * user can cycle through all 41 supported time zones. - * - * - A *long press* on the *light button* selects the current time zone, - * and the signal indicator appears at the top left. Another *long - * press* of the *light button* deselects the time zone. - * - * - A *long press* on the *alarm button* exits settings mode and returns - * to display mode. - * - * ### Display mode - * - * In the display mode, the face shows the time of the currently selected - * time zone. The face includes the following components: - * - * - The top of the face displays the first two letters of the time zone - * abbreviation, such as "PS" for Pacific Standard Time or "CE" for - * Central European Time. - * - * - The upper-right corner shows the current day of the month, which - * helps indicate time zones that cross the international date line - * with respect to the local time. - * - * - The main display shows the time in the selected time zone in either - * 12-hour or 24-hour form. There is no timeout, allowing users to keep - * the chosen time zone displayed for as long as they wish. - * - * The user can navigate through the selected time zones using the - * following buttons: - * - * - The *alarm button* moves to the next selected time zone, while the - * light button moves to the *previous zone*. If no time zone is - * selected, the face simply shows UTC. - * - * - A *long press* on the *alarm button* enters settings mode and - * enables the user to re-configure the selected time zones. - * - * - A *long press* on the *light button* activates the LED illumination - * of the watch. - * - */ - #include <stdlib.h> #include <string.h> #include "world_clock2_face.h" diff --git a/movement/watch_faces/clock/world_clock2_face.h b/movement/watch_faces/clock/world_clock2_face.h index f70dca1f..0baac212 100644 --- a/movement/watch_faces/clock/world_clock2_face.h +++ b/movement/watch_faces/clock/world_clock2_face.h @@ -26,6 +26,65 @@ #ifndef WORLD_CLOCK2_FACE_H_ #define WORLD_CLOCK2_FACE_H_ +/* + * WORLD CLOCK 2 + * + * This is an alternative world clock face that allows the user to cycle + * through a list of selected time zones. It extends the original + * implementation by Joey Castillo. The face has two modes: display mode + * and settings mode. + * + * Settings mode + * + * When the clock face is activated for the first time, it enters settings + * mode. Here, the user can select the time zones they want to display. The + * face shows a summary of the current time zone: + * * The top of the face displays the first two letters of the time zone + * abbreviation, such as "PS" for Pacific Standard Time or CE for + * "Central European Time". + * * The upper-right corner shows the index number of the time zone. This + * helps avoid confusion when multiple time zones have the same two-letter + * abbreviation. + * * The main display shows the offset from UTC, with a "+" indicating a + * positive offset and a "-" indicating a negative offset. For example, + * the offset for Japanese Standard Time is displayed as "+9:00". + * + * The user can navigate through the time zones and select them using the + * following buttons: + * * The ALARM button moves forward to the next time zone, while the LIGHT + * button moves backward to the previous zone. This way, the user can + * cycle through all 41 supported time zones. + * * A long press on the LIGHT button selects the current time zone, and + * the signal indicator appears at the top left. Another long press of + * the LIGHT button deselects the time zone. + * * A long press on the ALARM button exits settings mode and returns to + * display mode. + * + * Display mode + * + * In the display mode, the face shows the time of the currently selected + * time zone. The face includes the following components: + * * The top of the face displays the first two letters of the time zone + * abbreviation, such as "PS" for Pacific Standard Time or "CE" for + * Central European Time. + * * The upper-right corner shows the current day of the month, which helps + * indicate time zones that cross the international date line with respect + * to the local time. + * * The main display shows the time in the selected time zone in either + * 12-hour or 24-hour form. There is no timeout, allowing users to keep + * the chosen time zone displayed for as long as they wish. + * + * The user can navigate through the selected time zones using the following + * buttons: + * * The ALARM button moves to the next selected time zone, while the LIGHT + * button moves to the previous zone. If no time zone is selected, the + * face simply shows UTC. + * * A long press on the ALARM button enters settings mode and enables the + * user to re-configure the selected time zones. + * * A long press on the LIGHT button activates the LED illumination of the + * watch. + */ + /* Number of zones. See movement_timezone_offsets. */ #define NUM_TIME_ZONES 41 diff --git a/movement/watch_faces/clock/world_clock_face.h b/movement/watch_faces/clock/world_clock_face.h index 669dcaa8..92e91a6f 100644 --- a/movement/watch_faces/clock/world_clock_face.h +++ b/movement/watch_faces/clock/world_clock_face.h @@ -25,7 +25,29 @@ #ifndef WORLD_CLOCK_FACE_H_ #define WORLD_CLOCK_FACE_H_ +/* + * WORLD CLOCK FACE + * + * The World Clock watch face looks similar to the Simple Clock watch face, + * but you’ll notice that at first launch the day of week indicators are blank. + * That’s because this watch face does not display the day of the week. + * Instead, you may customize these letters to display the name of a time zone + * of your choosing. + * + * To customize this watch face, press and hold the ALARM button. The first + * letter in the top row will begin flashing. Press the ALARM button repeatedly + * to advance through the available letters in the first slot, then press the + * LIGHT button to move to the second letter. Finally, press LIGHT again to move + * to the time zone setting, and press ALARM to cycle through the available time + * zones. Press LIGHT one last time to return to the world clock display. + * + * Note that the second slot cannot display all letters or numbers. Also note + * that at this time, time zones do not automatically update for daylight saving + * time; you will need to manually adjust this field each spring and fall. + */ + #include "movement.h" + typedef union { struct { uint8_t char_0; diff --git a/movement/watch_faces/clock/wyoscan_face.h b/movement/watch_faces/clock/wyoscan_face.h index 68db9eed..d20e7636 100644 --- a/movement/watch_faces/clock/wyoscan_face.h +++ b/movement/watch_faces/clock/wyoscan_face.h @@ -25,15 +25,32 @@ #ifndef WYOSCAN_FACE_H_ #define WYOSCAN_FACE_H_ -#include "movement.h" - /* - * A DESCRIPTION OF YOUR WATCH FACE + * WYOSCAN .5 hz watchface + * + * This is a recreation of the Wyoscan watch, which was a $175 watch in 2014. + * It was an f-91w pcb replacement. + * + * Video: https://user-images.githubusercontent.com/1795778/252550124-e07f0ed1-e328-4337-a654-fa1ee65d883f.mp4 + * Background information: https://artmetropole.com/shop/11460 + * Demo of what it looks like: https://www.o-r-g.com/apps/wyoscan + * + * 8 frames per number * 6 numbers + the trailing 16 frames = 64 frames + * at 32 frames per second, this is a 2-second cycle time or 0.5 Hz. * - * and a description of how use it + * It is giving me a stack overflow after about 2.5 cycles of the time display + * in the emulator, but it works fine on the watch. + * + * I'd like to make something for the low energy mode, but I haven't thought + * about how that might work, right now it just freezes in low energy mode + * until you press the 12-24HR button. + * + * There are no controls; it simply animates as long as the page is active. * */ +#include "movement.h" + #define MAX_ILLUMINATED_SEGMENTS 16 typedef struct { diff --git a/movement/watch_faces/complication/activity_face.h b/movement/watch_faces/complication/activity_face.h index c72f7099..552ef4d8 100644 --- a/movement/watch_faces/complication/activity_face.h +++ b/movement/watch_faces/complication/activity_face.h @@ -25,10 +25,8 @@ #ifndef ACTIVITY_FACE_H_ #define ACTIVITY_FACE_H_ -#include "movement.h" - /* - * ACTIVITY WATCH FACE + * ACTIVITY watch face * * The Activity face lets you record activities like you would do with a fitness watch. * It supports different activities like running, biking, rowing etc., and for each recorded activity @@ -69,9 +67,10 @@ * * See the top of activity_face.c for some customization options. What you most likely want to do * is reduce the list of activities shown on the first screen to the ones you are regularly doing. - * */ +#include "movement.h" + void activity_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); void activity_face_activate(movement_settings_t *settings, void *context); bool activity_face_loop(movement_event_t event, movement_settings_t *settings, void *context); diff --git a/movement/watch_faces/complication/alarm_face.c b/movement/watch_faces/complication/alarm_face.c index 3b7d1e35..3cacc983 100644 --- a/movement/watch_faces/complication/alarm_face.c +++ b/movement/watch_faces/complication/alarm_face.c @@ -22,8 +22,6 @@ * SOFTWARE. */ -//----------------------------------------------------------------------------- - #include <stdlib.h> #include <string.h> @@ -32,31 +30,6 @@ #include "watch_utility.h" #include "watch_private_display.h" -/* - Implements 16 alarm slots on the sensor watch - - Usage: - - In normal mode, the alarm button cycles through all 16 alarms. - - Pressing the alarm button long in normal mode toggles the corresponding alarm on or off. - (Whereas pressing the alarm button extra long brings you back to alarm no. 1.) - - Pressing the light button enters setting mode and cycles through the settings of each alarm. - (Long pressing the light button enters setting mode without illuminating the led.) - - In setting mode an alarm slot is selected by pressing the alarm button when the slot number - in the upper right corner is blinking. - - For each alarm slot, you can select the day. These are the day modes: - - ED = the alarm rings every day - - 1t = the alarm fires only one time and is erased afterwards - - MF = the alarm fires Mondays to Fridays - - WN = the alarm fires on weekends (Sa/Su) - - MO to SU = the alarm fires only on the given day of week - - You can fast cycle through hour or minute setting via long press of the alarm button. - - You can select the tone in which the alarm is played. (Three pitch levels available.) - - You can select how many "beep rounds" are played for each alarm. 1 to 9 rounds, plus extra - long ('L') and extra short ('o') alarms. - - The simple watch face indicates if any alarm is set within the next 24h by showing the signal - indicator. -*/ - typedef enum { alarm_setting_idx_alarm, alarm_setting_idx_day, diff --git a/movement/watch_faces/complication/alarm_face.h b/movement/watch_faces/complication/alarm_face.h index dafbee5e..1c22948e 100644 --- a/movement/watch_faces/complication/alarm_face.h +++ b/movement/watch_faces/complication/alarm_face.h @@ -27,11 +27,34 @@ #ifndef ALARM_FACE_H_ #define ALARM_FACE_H_ -#include "movement.h" - /* -A face for setting various alarms -*/ + * ALARM face + * + * Implements up to 16 alarm slots on the sensor watch + * + * Usage: + * - In normal mode, the alarm button cycles through all 16 alarms. + * - Pressing the alarm button long in normal mode toggles the corresponding alarm on or off. + * (Whereas pressing the alarm button extra long brings you back to alarm no. 1.) + * - Pressing the light button enters setting mode and cycles through the settings of each alarm. + * (Long pressing the light button enters setting mode without illuminating the led.) + * - In setting mode an alarm slot is selected by pressing the alarm button when the slot number + * in the upper right corner is blinking. + * - For each alarm slot, you can select the day. These are the day modes: + * - ED = the alarm rings every day + * - 1t = the alarm fires only one time and is erased afterwards + * - MF = the alarm fires Mondays to Fridays + * - WN = the alarm fires on weekends (Sa/Su) + * - MO to SU = the alarm fires only on the given day of week + * - You can fast cycle through hour or minute setting via long press of the alarm button. + * - You can select the tone in which the alarm is played. (Three pitch levels available.) + * - You can select how many "beep rounds" are played for each alarm. 1 to 9 rounds, plus extra + * long ('L') and extra short ('o') alarms. + * - The simple watch face indicates if any alarm is set within the next 24h by showing the signal + * indicator. + */ + +#include "movement.h" #define ALARM_ALARMS 16 // no of available alarm slots (be aware: only 4 bits reserved for this value in struct below) #define ALARM_DAY_STATES 11 // no of different day settings diff --git a/movement/watch_faces/complication/astronomy_face.h b/movement/watch_faces/complication/astronomy_face.h index f956955b..6ab22113 100644 --- a/movement/watch_faces/complication/astronomy_face.h +++ b/movement/watch_faces/complication/astronomy_face.h @@ -25,6 +25,47 @@ #ifndef ASTRONOMY_FACE_H_ #define ASTRONOMY_FACE_H_ +/* + * ASTRONOMY face + * + * The Astronomy watch face is among the most complex watch faces in the + * Movement collection. It allows you to calculate the locations of celestial + * bodies in the sky, as well as distance in astronomical units (or, in the + * case of the Moon, distance in kilometers). + * + * When you arrive at the Astronomy watch face, you’ll see its name (“Astro”) + * and an animation of two objects orbiting each other. You will also see “SO” + * (for Sol) flashing in the top left. The flashing letters indicate the + * currently selected celestial body. Short press Alarm to advance through + * the available celestial bodies: + * + * SO - Sol, the sun + * ME - Mercury + * VE - Venus + * LU - Luna, the Earth’s moon + * MA - Mars + * JU - Jupiter + * SA - Saturn + * UR - Uranus + * NE - Neptune + * + * Once you’ve selected the celestial body whose parameters you wish to + * calculate, long press the Alarm button and release it. The letter “C” will + * flash while the calculation is performed. + * + * When the calculation is complete, the screen will display the altitude + * (“aL”) of the celestial body. You can cycle through the available parameters + * with repeated short presses on the Alarm button: + * + * aL - Altitude (in degrees), the elevation over the horizon. If negative, it is below the horizon. + * aZ - Azimuth (in degrees), the cardinal direction relative to true north. + * rA - Right Ascension (in hours/minutes/seconds) + * dE - Declination (in degrees/minutes/seconds) + * di - Distance (the digits in the top right will display either aU for astronomical units, or K for kilometers) + * + * Long press on the Alarm button to select another celestial body. + */ + #include "movement.h" #include "astrolib.h" diff --git a/movement/watch_faces/complication/blinky_face.h b/movement/watch_faces/complication/blinky_face.h index e966ab1d..f453de64 100644 --- a/movement/watch_faces/complication/blinky_face.h +++ b/movement/watch_faces/complication/blinky_face.h @@ -25,6 +25,32 @@ #ifndef BLINKY_FACE_H_ #define BLINKY_FACE_H_ +/* + * BLINKY LIGHT face + * + * The blinky light watch face was designed as a tutorial for making a watch + * face in Movement, but it actually might be useful to have a blinking light + * in a pinch. + * + * The screen displays the name of the watch face (”BL”), as well as an S at + * the top right for slow blink or an F for fast blink. The bottom line selects + * the color: green, red or yellow. You can change the speed of the blinking + * light by pressing the Alarm button, and change the color with the Light + * button. A long press on the Alarm button starts the blinking light, and + * another long press stops it. + * + * Note that this will chew through your battery! The green LED uses about + * 450µA at full brightness, which is 45 times the normal power consumption of + * the watch. The red LED is an order of magnitude less efficient (4500 µA), + * and the yellow setting lights both LEDs, which chews through nearly + * 5 milliamperes. This means that one hour of yellow blinking is likely to + * eat up between 2 and 3 percent of the battery’s usable life! + * + * Still, if you need to signal your location to someone in a dark forest, + * this watch face could come in handy. Just try to use the green LED as much + * as you can. + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/complication/breathing_face.h b/movement/watch_faces/complication/breathing_face.h index 2ff947a2..fd603f19 100644 --- a/movement/watch_faces/complication/breathing_face.h +++ b/movement/watch_faces/complication/breathing_face.h @@ -25,6 +25,17 @@ #ifndef BREATHING_FACE_H_ #define BREATHING_FACE_H_ +/* + * BOXED BREATHING face + * + * Breathing is a complication for guiding boxed breathing sessions. + * Boxed breathing is a technique to help you stay calm and improve + * concentration in stressful situations. + * + * Usage: Timed messages will cycle as long as this face is active. + * Press ALARM to toggle sound. + */ + #include "movement.h" void breathing_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/movement/watch_faces/complication/couch_to_5k_face.c b/movement/watch_faces/complication/couch_to_5k_face.c new file mode 100644 index 00000000..8aa3fe66 --- /dev/null +++ b/movement/watch_faces/complication/couch_to_5k_face.c @@ -0,0 +1,267 @@ +/* + * MIT License + * + * Copyright (c) 2023 Ekaitz Zarraga <ekaitz@elenq.tech> + * + * 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 "couch_to_5k_face.h" + +// They go: Warmup, Run, Walk, Run, Walk, Run, Walk ... , End (0) +// Time is defined in seconds +// Maybe do /10 to reduce memory usage? +// (i don't want to use floats) + +// uint16_t C25K_WEEK_TEST[] = {10, 10, 10, 0}; +uint16_t C25K_WEEK_1[] = {300, 60, 90, 60, 90, 60, 90, 60, 90, 60, 90, 60, + 90, 60, 90, 60, 90, 0}; +uint16_t C25K_WEEK_2[] = {300, 90, 120, 90, 120, 90, 120, 90, 120, 90, 120, + 90, 120, 0}; +uint16_t C25K_WEEK_3[] = {300, 90, 90, 180, 180, 90, 90, 180, 180, 0}; +uint16_t C25K_WEEK_4[] = {300, 180, 90, 300, 150, 180, 90, 300, 0}; +uint16_t C25K_WEEK_5_1[] = {300, 300, 180, 300, 180, 300, 0 }; +uint16_t C25K_WEEK_5_2[] = {300, 480, 300, 480 , 0}; +uint16_t C25K_WEEK_5_3[] = {300, 1200, 0}; +uint16_t C25K_WEEK_6_1[] = {300, 300, 180, 480, 180, 300, 0 }; +uint16_t C25K_WEEK_6_2[] = {300, 600, 180, 600 , 0}; +uint16_t C25K_WEEK_6_3[] = {300, 1500, 0}; +uint16_t C25K_WEEK_7[] = {300, 1500, 0}; +uint16_t C25K_WEEK_8[] = {300, 1680, 0}; +uint16_t C25K_WEEK_9[] = {300, 1800, 0}; + + +#define C25K_SESSIONS_LENGTH 3*9 +uint16_t *C25K_SESSIONS[C25K_SESSIONS_LENGTH]; + +static inline bool _finished(couch_to_5k_state_t *state){ + return state->exercise_type == C25K_FINISHED; +} +static inline bool _cleared(couch_to_5k_state_t *state){ + return state->timer == C25K_SESSIONS[state->session][0] + && state->exercise == 0; +} +static inline void _next_session(couch_to_5k_state_t *state){ + if (++state->session >= C25K_SESSIONS_LENGTH){ + state->session = 0; + } +} + +static inline void _assign_exercise_type(couch_to_5k_state_t *state){ + if (state->exercise == 0){ + state->exercise_type = C25K_WARMUP; + } else if (state->exercise % 2 == 1){ + state->exercise_type = C25K_RUN; + } else { + state->exercise_type = C25K_WALK; + } +} + +static void _next_exercise(couch_to_5k_state_t *state){ + state->exercise++; + state->timer = C25K_SESSIONS[state->session][state->exercise]; + // If the new timer starts in zero, it's finished + if (state->timer == 0){ + movement_play_alarm_beeps(7, BUZZER_NOTE_C8); + state->exercise_type = C25K_FINISHED; + return; + } + movement_play_alarm_beeps(4, BUZZER_NOTE_A7); + _assign_exercise_type(state); +} + +static void _init_session(couch_to_5k_state_t *state){ + state->exercise = 0; // Restart exercise counter + state->timer = C25K_SESSIONS[state->session][state->exercise]; + _assign_exercise_type(state); +} + +static char *_exercise_type_to_str(exercise_type_t t){ + switch (t){ + case C25K_WARMUP: + return "WU"; + case C25K_RUN: + return "RU"; + case C25K_WALK: + return "WA"; + case C25K_FINISHED: + return "--"; + default: + return " "; + } +} +static void _display(couch_to_5k_state_t *state, char *buf){ + // TODO only repaint needed parts + uint8_t seconds = state->timer % 60; + sprintf(buf, "%s%2d%2d%02d%02d", + _exercise_type_to_str(state->exercise_type), + (state->session + 1) % 100, + ((state->timer - seconds) / 60) % 100, + seconds, + (state->exercise + 1) % 100); + watch_display_string(buf, 0); +} + + +void couch_to_5k_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(couch_to_5k_state_t)); + memset(*context_ptr, 0, sizeof(couch_to_5k_state_t)); + // Do any one-time tasks in here; the inside of this conditional + // happens only at boot. + // C25K_SESSIONS[0] = C25K_WEEK_TEST; + C25K_SESSIONS[0] = C25K_WEEK_1; + C25K_SESSIONS[1] = C25K_WEEK_1; + C25K_SESSIONS[2] = C25K_WEEK_1; + C25K_SESSIONS[3] = C25K_WEEK_2; + C25K_SESSIONS[4] = C25K_WEEK_2; + C25K_SESSIONS[5] = C25K_WEEK_2; + C25K_SESSIONS[6] = C25K_WEEK_3; + C25K_SESSIONS[7] = C25K_WEEK_3; + C25K_SESSIONS[8] = C25K_WEEK_3; + C25K_SESSIONS[9] = C25K_WEEK_4; + C25K_SESSIONS[10] = C25K_WEEK_4; + C25K_SESSIONS[11] = C25K_WEEK_4; + C25K_SESSIONS[12] = C25K_WEEK_5_1; + C25K_SESSIONS[13] = C25K_WEEK_5_2; + C25K_SESSIONS[14] = C25K_WEEK_5_3; + C25K_SESSIONS[15] = C25K_WEEK_6_1; + C25K_SESSIONS[16] = C25K_WEEK_6_2; + C25K_SESSIONS[17] = C25K_WEEK_6_3; + C25K_SESSIONS[18] = C25K_WEEK_7; + C25K_SESSIONS[19] = C25K_WEEK_7; + C25K_SESSIONS[20] = C25K_WEEK_7; + C25K_SESSIONS[21] = C25K_WEEK_8; + C25K_SESSIONS[22] = C25K_WEEK_8; + C25K_SESSIONS[23] = C25K_WEEK_8; + C25K_SESSIONS[24] = C25K_WEEK_9; + C25K_SESSIONS[25] = C25K_WEEK_9; + C25K_SESSIONS[26] = C25K_WEEK_9; + } + // Do any pin or peripheral setup here; this will be called whenever the + // watch wakes from deep sleep. +} + +void couch_to_5k_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + // Handle any tasks related to your watch face coming on screen. + watch_set_colon(); +} + + +bool couch_to_5k_face_loop(movement_event_t event, movement_settings_t *settings, + void *context) { + couch_to_5k_state_t *state = (couch_to_5k_state_t *)context; + static char buf[11]; + static bool paused = true; + + switch (event.event_type) { + case EVENT_ACTIVATE: + // Show your initial UI here. + movement_request_tick_frequency(1); + _init_session(state); + paused = true; + _display(state, buf); + break; + case EVENT_TICK: + if ( !paused && !_finished(state) ) { + if (state->timer == 0){ + _next_exercise(state); + } else { + state->timer--; + } + } + _display(state, buf); + break; + case EVENT_LIGHT_BUTTON_UP: + // This is the next-exercise / reset button. + + // When finished move to the next session and leave it paused + if ( _finished(state) ){ + _next_session(state); + _init_session(state); + paused = true; + break; + } + // When paused and cleared move to next, when only paused, clear + if ( paused ) { + if ( _cleared(state) ){ + _next_session(state); + } + _init_session(state); + } + break; + case EVENT_ALARM_BUTTON_UP: + if (settings->bit.button_should_sound) { + watch_buzzer_play_note(BUZZER_NOTE_C8, 50); + } + paused = !paused; + break; + case EVENT_TIMEOUT: + // Your watch face will receive this event after a period of + // inactivity. If it makes sense to resign, + movement_move_to_face(0); + break; + case EVENT_LOW_ENERGY_UPDATE: + // If you did not resign in EVENT_TIMEOUT, you can use this event + // to update the display once a minute. Avoid displaying + // fast-updating values like seconds, since the display won't + // update again for 60 seconds. You should also consider starting + // the tick animation, to show the wearer that this is sleep mode: + // watch_start_tick_animation(500); + break; + default: + // Movement's default loop handler will step in for any cases you + // don't handle above: + // * EVENT_LIGHT_BUTTON_DOWN lights the LED + // * EVENT_MODE_BUTTON_UP moves to the next watch face in the list + // * EVENT_MODE_LONG_PRESS returns to the first watch face (or + // skips to the secondary watch face, if configured) + // You can override any of these behaviors by adding a case for + // these events to this switch statement. + return movement_default_loop_handler(event, settings); + } + + // return true if the watch can enter standby mode. Generally speaking, you + // should always return true. + // Exceptions: + // * If you are displaying a color using the low-level watch_set_led_color + // function, you should return false. + // * If you are sounding the buzzer using the low-level + // watch_set_buzzer_on function, you should return false. + // Note that if you are driving the LED or buzzer using Movement functions + // like movement_illuminate_led or movement_play_alarm, you can still + // return true. This guidance only applies to the low-level watch_ + // functions. + return true; +} + +void couch_to_5k_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + + // handle any cleanup before your watch face goes off-screen. +} + diff --git a/movement/watch_faces/complication/couch_to_5k_face.h b/movement/watch_faces/complication/couch_to_5k_face.h new file mode 100644 index 00000000..3c36ce6f --- /dev/null +++ b/movement/watch_faces/complication/couch_to_5k_face.h @@ -0,0 +1,87 @@ +/* + * MIT License + * + * Copyright (c) 2023 Ekaitz Zarraga <ekaitz@elenq.tech> + * + * 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 COUCHTO5K_FACE_H_ +#define COUCHTO5K_FACE_H_ + +#include "movement.h" + +/* + * Couch To 5k; + * + * + * The program is designed to train 3 times a week. Each training is a + * *session*. Each of the rounds you have in the training is an *exercise*. + * + * The training goes like this: + * 5min warm-up walk -> Run X minutes -> Walk Y minutes -> ... -> Stop + * + * The watch face shows it like this: The weekday indicator shows if you need + * to Warm Up (`WU`), run (`rU`), walk (`WA`) or stop (`--`). + * + * The month-day indicator shows the session you are in (from 1 to 27). + * + * The timer shows the time you have left in the exercise and the exercise you + * are doing (MM:SS:ee). When an exercise finishes you are notified with an + * alarm. When the whole session finishes, a different tone is played for a + * longer period. + * + * Pressing the ALARM button pauses/resumes the clock. + * + * Pressing the LIGHT button does nothing if the timer is not paused. When it + * is paused it clears the current session (it restarts it to the beginning) + * and if it was already cleared or the current session was finished moves to + * the next session. + */ + +typedef enum { + C25K_WARMUP, + C25K_RUN, + C25K_WALK, + C25K_FINISHED +} exercise_type_t; + +typedef struct { + // Anything you need to keep track of, put it here! + uint8_t session; + uint8_t exercise; + exercise_type_t exercise_type; + uint16_t timer; +} couch_to_5k_state_t; + +void couch_to_5k_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void couch_to_5k_face_activate(movement_settings_t *settings, void *context); +bool couch_to_5k_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void couch_to_5k_face_resign(movement_settings_t *settings, void *context); + +#define couch_to_5k_face ((const watch_face_t){ \ + couch_to_5k_face_setup, \ + couch_to_5k_face_activate, \ + couch_to_5k_face_loop, \ + couch_to_5k_face_resign, \ + NULL, \ +}) + +#endif // COUCHTO5K_FACE_H_ + diff --git a/movement/watch_faces/complication/countdown_face.c b/movement/watch_faces/complication/countdown_face.c index aa23ddc6..be04040e 100644 --- a/movement/watch_faces/complication/countdown_face.c +++ b/movement/watch_faces/complication/countdown_face.c @@ -23,27 +23,12 @@ * SOFTWARE. */ -//----------------------------------------------------------------------------- - #include <stdlib.h> #include <string.h> #include "countdown_face.h" #include "watch.h" #include "watch_utility.h" -/* - Slight extension of the original countdown face by Wesley Ellis. - - - Press the light button to enter setting mode and adjust the - countdown timer. - - - Start and pause the countdown using the alarm button, similar to the - stopwatch face. - - - When paused or terminated, press the light button to restore the - last entered countdown. -*/ - #define CD_SELECTIONS 3 #define DEFAULT_MINUTES 3 diff --git a/movement/watch_faces/complication/countdown_face.h b/movement/watch_faces/complication/countdown_face.h index 12bb1d1e..1fe7c37e 100644 --- a/movement/watch_faces/complication/countdown_face.h +++ b/movement/watch_faces/complication/countdown_face.h @@ -22,22 +22,27 @@ * SOFTWARE. */ -//----------------------------------------------------------------------------- - #ifndef COUNTDOWN_FACE_H_ #define COUNTDOWN_FACE_H_ -#include "movement.h" - /* -A countdown/timer face - -Max countdown is 23 hours, 59 minutes and 59 seconds. - -Note: we have to prevent the watch from going to deep sleep using -movement_schedule_background_task() while the timer is running. -*/ + * COUNTDOWN TIMER face + * + * Slight extension of the original countdown face by Wesley Ellis. + * - Press the light button to enter setting mode and adjust the + * countdown timer. + * - Start and pause the countdown using the alarm button, similar + * to the stopwatch face. + * - When paused or terminated, press the light button to restore the + * last entered countdown. + * + * Max countdown is 23 hours, 59 minutes and 59 seconds. + * + * Note: we have to prevent the watch from going to deep sleep using + * movement_schedule_background_task() while the timer is running. + */ +#include "movement.h" typedef enum { cd_paused, diff --git a/movement/watch_faces/complication/counter_face.h b/movement/watch_faces/complication/counter_face.h index 85f203e9..3ac6a9b6 100644 --- a/movement/watch_faces/complication/counter_face.h +++ b/movement/watch_faces/complication/counter_face.h @@ -25,9 +25,19 @@ #ifndef COUNTER_FACE_H_ #define COUNTER_FACE_H_ +/* + * COUNTER face + * + * Counter face is designed to count the number of running laps during exercises. + * + * Usage: + * Short-press ALARM to increment the counter (loops at 99) + * Long-press ALARM to reset the counter. + * Long-press LIGHT to toggle sound. + */ + #include "movement.h" -// Counter face is designed to count the number of running laps during excercises. typedef struct { uint8_t counter_idx; bool beep_on; diff --git a/movement/watch_faces/complication/databank_face.c b/movement/watch_faces/complication/databank_face.c index 9bc22da9..8be54a66 100644 --- a/movement/watch_faces/complication/databank_face.c +++ b/movement/watch_faces/complication/databank_face.c @@ -20,8 +20,6 @@ * 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. - * - * Displays some pre-defined data that you might want to remember. Math constants, birthdays, phone numbers... */ #include <stdlib.h> @@ -96,12 +94,8 @@ bool databank_face_loop(movement_event_t event, movement_settings_t *settings, v case EVENT_ACTIVATE: display(); case EVENT_TICK: - // on activate and tick, if we are animating, break; case EVENT_LIGHT_BUTTON_UP: - // when the user presses 'light', we illuminate the LED. We could override this if - // our UI needed an additional button for input, consuming the light button press - // but not illuminating the LED. databank_state.current_word = (databank_state.current_word + max_words - 1) % max_words; display(); break; @@ -116,8 +110,6 @@ bool databank_face_loop(movement_event_t event, movement_settings_t *settings, v display(); break; case EVENT_ALARM_BUTTON_UP: - // when the user presses 'alarm', we toggle the state of the animation. If animating, - // we stop; if stopped, we resume. databank_state.current_word = (databank_state.current_word + 1) % max_words; display(); break; diff --git a/movement/watch_faces/complication/databank_face.h b/movement/watch_faces/complication/databank_face.h index 1f204b22..3376cefa 100644 --- a/movement/watch_faces/complication/databank_face.h +++ b/movement/watch_faces/complication/databank_face.h @@ -25,6 +25,23 @@ #ifndef DATABANK_FACE_H_ #define DATABANK_FACE_H_ +/* + * DATABANK face + * + * Displays some pre-defined data that you might want to remember + * Math constants, birthdays, phone numbers... + * + * Usage: Edit the global variable `pi_data` in "databank_face.c" + * to the define the data that will be displayed. Each "item" contains + * a two-letter label (using the day-of-week display), then a longer + * string that will be displayed one "word" (six characters) at a time. + * + * Short-press ALARM to display the next word. + * Short-press LIGHT to display the previous word. + * Long-press ALARM to display the next item. + * Long-press LIGHT to display the previous item. + */ + #include "movement.h" void databank_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/movement/watch_faces/complication/day_one_face.h b/movement/watch_faces/complication/day_one_face.h index db1d05ed..9a59e3bb 100644 --- a/movement/watch_faces/complication/day_one_face.h +++ b/movement/watch_faces/complication/day_one_face.h @@ -25,10 +25,29 @@ #ifndef DAY_ONE_FACE_H_ #define DAY_ONE_FACE_H_ -#include "movement.h" +/* + * DAY ONE face + * + * This watch face displays the number of days since or until a given date. + * It was originally designed to display the number of days you’ve been alive, + * but technically it can count up from any date in the 20th century or the + * 21st century, so far. + * + * Long press on the Alarm button to enter customization mode. The text “YR” + * will appear, and will allow you to set the year starting from 1959. Press + * Alarm repeatedly to advance the year. If your birthday is before 1959, + * advance beyond the current year and it will wrap around to 1900. + * + * Once you have set the year, press Light to set the month (“MO”) and + * day (“DA”), advancing the value by pressing Alarm repeatedly. + * + * Note that at this time, the Day One face does not display the sleep + * indicator in sleep mode, which may make the watch appear to be + * unresponsive in sleep mode. You can still press the Alarm button to + * wake the watch. This UI quirk will be addressed in a future update. + */ -// The Day One face is designed to count the days since or until a given date. It also functions as an -// interface for setting the birth date register, which other watch faces can use for various purposes. +#include "movement.h" typedef enum { PAGE_DISPLAY, diff --git a/movement/watch_faces/complication/discgolf_face.c b/movement/watch_faces/complication/discgolf_face.c index 0852bf1f..7b5142ab 100644 --- a/movement/watch_faces/complication/discgolf_face.c +++ b/movement/watch_faces/complication/discgolf_face.c @@ -1,7 +1,7 @@ #include <stdlib.h> #include <string.h> #include "discgolf_face.h" -#include "watch.h" // Remember to change number of courses in this file +#include "watch.h" // Remember to change number of courses in this file #include "watch_utility.h" /* diff --git a/movement/watch_faces/complication/discgolf_face.h b/movement/watch_faces/complication/discgolf_face.h index 5e8068e0..d168958c 100644 --- a/movement/watch_faces/complication/discgolf_face.h +++ b/movement/watch_faces/complication/discgolf_face.h @@ -22,21 +22,24 @@ * SOFTWARE. */ -///////////////////////////////////////////////////////////////////////////////////// +#ifndef DISCGOLF_FACE_H_ +#define DISCGOLF_FACE_H_ -/* +/* + * DISC GOLF face + * * Keep track of scores in discgolf or golf! * The watch face operates in three different modes: * * - dg_setting: Select a course * Enter this mode by holding down the light button. The screen will display - * the label for the hole and the lowest score since last boot. - * Press alarm to loop through the holes. Press the light button to make a + * the label for the hole and the lowest score since last boot. + * Press alarm to loop through the holes. Press the light button to make a * selection. This will reset all scores and start a new game in dg_idle mode. * * -dg_idle: We're playing a hole * This either shows your current score relative to par, or the score for a - * particular hole. + * particular hole. * At the start of a game, press alarm to loop through the holes and leave it * your starting hole. For optimal experience, play the course linearly after that * If you're viewing the hole you're supposed to be playing, the watch face will @@ -49,19 +52,15 @@ * -dg_scoring: Input score for a hole * In this mode, if the score is 0 (hasn't been entered during this round), * it will blink, indicating we're in scoring mode. Press the alarm button - * to increment the score up until 15, in which case it loops back to 0. + * to increment the score up until 15, in which case it loops back to 0. * Press the light button to save the score for that hole, advance one hole * if you're not editing an already input score, and returning to idle mode. * - * When all scores have been entered, the LAP indicator turns on. At that point, if we enter - * dg_setting to select a course, the score for that round is evaluated against the current + * When all scores have been entered, the LAP indicator turns on. At that point, if we enter + * dg_setting to select a course, the score for that round is evaluated against the current * lowest score for that course, and saved if it is better. */ - -#ifndef DISCGOLF_FACE_H_ -#define DISCGOLF_FACE_H_ - #include "movement.h" #define courses 11 @@ -75,7 +74,7 @@ typedef struct { uint8_t course; // Index for course selection, from 0 uint8_t hole; // Index for current hole, from 1 uint8_t playing; // Current hole - int scores[18]; // Scores for each played hole + int scores[18]; // Scores for each played hole discgolf_mode_t mode; // Watch face mode } discgolf_state_t; diff --git a/movement/watch_faces/complication/dual_timer_face.h b/movement/watch_faces/complication/dual_timer_face.h index d7c6cfa0..d1ac7935 100644 --- a/movement/watch_faces/complication/dual_timer_face.h +++ b/movement/watch_faces/complication/dual_timer_face.h @@ -26,16 +26,6 @@ #ifndef DUAL_TIMER_FACE_H_ #define DUAL_TIMER_FACE_H_ -#include "movement.h" - -/* - * IMPORTANT: This watch face uses the same TC2 callback counter as the Stock Stopwatch - * watch-face. It works through calling a global handler function. The two watch-faces - * therefore can't coexist within the same firmware. If you want to compile this watch-face - * then you need to remove the line <../watch_faces/complication/stock_stopwatch_face.c \> - * from the Makefile. - */ - /* * DUAL TIMER * ========== @@ -70,8 +60,15 @@ * the timers. In this case LONG PRESSING MODE will move to the next face instead of moving * back to the default watch face. * + * IMPORTANT: This watch face uses the same TC2 callback counter as the Stock Stopwatch + * watch-face. It works through calling a global handler function. The two watch-faces + * therefore can't coexist within the same firmware. If you want to compile this watch-face + * then you need to remove the line <../watch_faces/complication/stock_stopwatch_face.c \> + * from the Makefile. */ +#include "movement.h" + typedef struct { uint8_t centiseconds : 7; // 0-59 uint8_t seconds : 6; // 0-59 diff --git a/movement/watch_faces/complication/flashlight_face.h b/movement/watch_faces/complication/flashlight_face.h index 26750155..8c0ef8d5 100644 --- a/movement/watch_faces/complication/flashlight_face.h +++ b/movement/watch_faces/complication/flashlight_face.h @@ -25,9 +25,9 @@ #ifndef FLASHLIGHT_FACE_H_ #define FLASHLIGHT_FACE_H_ -#include "movement.h" - /* + * FLASHLIGHT face + * * A flashlight for use with the Flashlight sensor board. * * When the watch face appears, the display will show "FL" in the top two positions. @@ -35,6 +35,8 @@ * */ +#include "movement.h" + typedef struct { // Anything you need to keep track of, put it here! uint8_t unused; diff --git a/movement/watch_faces/complication/geomancy_face.h b/movement/watch_faces/complication/geomancy_face.h index 4a19ba85..4710ae7e 100644 --- a/movement/watch_faces/complication/geomancy_face.h +++ b/movement/watch_faces/complication/geomancy_face.h @@ -25,10 +25,8 @@ #ifndef GEOMANCY_FACE_H_ #define GEOMANCY_FACE_H_ -#include "movement.h" - /* - * GEOMANCY WATCH FACE + * GEOMANCY watch face * * A simple and straightforward watch face for the ancient Eastern geomantic divination system * of I Ching and the western system of "Geomancy". It is an optional addition to the Toss Up @@ -65,6 +63,8 @@ * */ +#include "movement.h" + typedef struct { uint8_t bits : 4; } nibble_t; diff --git a/movement/watch_faces/complication/habit_face.h b/movement/watch_faces/complication/habit_face.h index 80a4884f..d4f43cf4 100644 --- a/movement/watch_faces/complication/habit_face.h +++ b/movement/watch_faces/complication/habit_face.h @@ -25,8 +25,6 @@ #ifndef HABIT_FACE_H_ #define HABIT_FACE_H_ -#include "movement.h" - /* * Habit tracking face * @@ -36,6 +34,8 @@ * */ +#include "movement.h" + void habit_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr); void habit_face_activate(movement_settings_t *settings, void *context); diff --git a/movement/watch_faces/complication/interval_face.c b/movement/watch_faces/complication/interval_face.c index dabc6b1e..f4983236 100644 --- a/movement/watch_faces/complication/interval_face.c +++ b/movement/watch_faces/complication/interval_face.c @@ -22,8 +22,6 @@ * SOFTWARE. */ -//----------------------------------------------------------------------------- - #include <stdlib.h> #include <string.h> @@ -33,57 +31,6 @@ #include "watch_private_display.h" #include "watch_buzzer.h" -/* - This face brings 9 customizable interval timers to the sensor watch, - to be used as hiit training device and/or for time management techniques. - - - There are 9 interval timer slots, you can cycle through these with the - alarm button (short press). For each timer slot, a short "slideshow" - displaying the relevant details (like length of each phase - see below) - is shown. - - - To start an interval timer, press and hold the alarm button. - - - To pause a running timer, press the alarm button (short press). - - - To completely abort a running timer, press and hold the alarm button. - - - Press and hold the light button to enter settings mode for each interval - timer slot. - - - Each interval timer has 1 to 4 phases of customizable length like so: - (1) prepare/warum up --> (2) work --> (3) break --> (4) cool down. - When setting up or running a timer, each of these phases is displayed by - the letters "PR" (prepare), "WO" (work), "BR" (break), "CD" (cool down). - - - Each of these phases is optional, you can set the corresponding - minutes and seconds to zero. But at least one phase needs to be set, if - you want to use the timer. - - - You can define the number of rounds either only for the work - phase and/or for the combination of work + break phase. Let's say you - want an interval timer that counts 3 rounds of 30 seconds work, - followed by 20 seconds rest: - work 30s --> work 30s --> work 30s --> break 20s - You can do this by setting 30s for the "WO"rk phase and setting a 3 - in the lower right hand corner of the work page. The "LAP" indicator - lights up at this position, to explain that we are setting laps here. - After that, set the "BR"eak phase to 20s and leave the rest as it is. - - - If you want to set up a certain number of "full rounds", consisting - of work phase(s) plus breaks, you can do so at the "BR"eak page. The - number in the lower right hand corner determines the number of full - rounds to be counted. A "-" means, that there is no limit and the - timer keeps alternating between work and break phases. - - - This watch face comes with several pre-defined interval timers, - suitable for hiit training (timer slots 1 to 4) as well as doing - work according to the pomodoro principle (timer slots 5 to 6). - Feel free to adjust the timer slots to your own needs (or completely - wipe them ;-) - -*/ - typedef enum { interval_setting_0_timer_idx, interval_setting_1_clear_yn, diff --git a/movement/watch_faces/complication/interval_face.h b/movement/watch_faces/complication/interval_face.h index fa0a4280..79975855 100644 --- a/movement/watch_faces/complication/interval_face.h +++ b/movement/watch_faces/complication/interval_face.h @@ -22,16 +22,62 @@ * SOFTWARE. */ -//----------------------------------------------------------------------------- - #ifndef INTERVAL_FACE_H_ #define INTERVAL_FACE_H_ -#include "movement.h" - /* -A face for customizable interval timers -*/ + * INTERVAL TIMER face + * + * This face brings 9 customizable interval timers to the sensor watch, + * to be used as hiit training device and/or for time management techniques. + * + * - There are 9 interval timer slots, you can cycle through these with the + * alarm button (short press). For each timer slot, a short "slideshow" + * displaying the relevant details (like length of each phase - see below) + * is shown. + * + * - To start an interval timer, press and hold the alarm button. + * + * - To pause a running timer, press the alarm button (short press). + * + * - To completely abort a running timer, press and hold the alarm button. + * + * - Press and hold the light button to enter settings mode for each interval + * timer slot. + * + * - Each interval timer has 1 to 4 phases of customizable length like so: + * (1) prepare/warum up --> (2) work --> (3) break --> (4) cool down. + * When setting up or running a timer, each of these phases is displayed by + * the letters "PR" (prepare), "WO" (work), "BR" (break), "CD" (cool down). + * + * - Each of these phases is optional, you can set the corresponding + * minutes and seconds to zero. But at least one phase needs to be set, if + * you want to use the timer. + * + * - You can define the number of rounds either only for the work + * phase and/or for the combination of work + break phase. Let's say you + * want an interval timer that counts 3 rounds of 30 seconds work, + * followed by 20 seconds rest: + * work 30s --> work 30s --> work 30s --> break 20s + * You can do this by setting 30s for the "WO"rk phase and setting a 3 + * in the lower right hand corner of the work page. The "LAP" indicator + * lights up at this position, to explain that we are setting laps here. + * After that, set the "BR"eak phase to 20s and leave the rest as it is. + * + * - If you want to set up a certain number of "full rounds", consisting + * of work phase(s) plus breaks, you can do so at the "BR"eak page. The + * number in the lower right hand corner determines the number of full + * rounds to be counted. A "-" means, that there is no limit and the + * timer keeps alternating between work and break phases. + * + * - This watch face comes with several pre-defined interval timers, + * suitable for hiit training (timer slots 1 to 4) as well as doing + * work according to the pomodoro principle (timer slots 5 to 6). + * Feel free to adjust the timer slots to your own needs (or completely + * wipe them ;-) + */ + +#include "movement.h" #define INTERVAL_TIMERS 9 // no of available customizable timers (be aware: only 4 bits reserved for this value in struct below) diff --git a/movement/watch_faces/complication/invaders_face.h b/movement/watch_faces/complication/invaders_face.h index 59126dd5..37e91884 100644 --- a/movement/watch_faces/complication/invaders_face.h +++ b/movement/watch_faces/complication/invaders_face.h @@ -25,8 +25,6 @@ #ifndef INVADERS_FACE_H_ #define INVADERS_FACE_H_ -#include "movement.h" - /* * Remake of the "famous" Casio Number Invaders Game * @@ -60,6 +58,8 @@ * */ +#include "movement.h" + typedef struct { uint16_t highscore; bool sound_on; diff --git a/movement/watch_faces/complication/moon_phase_face.h b/movement/watch_faces/complication/moon_phase_face.h index 35d63183..9a64fcc7 100644 --- a/movement/watch_faces/complication/moon_phase_face.h +++ b/movement/watch_faces/complication/moon_phase_face.h @@ -25,6 +25,30 @@ #ifndef MOON_PHASE_FACE_H_ #define MOON_PHASE_FACE_H_ +/* + * MOON PHASE face + * + * The Moon Phase face is similar to the Sunrise/Sunset face: it displays the + * current phase of the moon, along with the day of the month and a graphical + * representation of the moon on the top row. + * + * This graphical representation is a bit abstract. The segments that turn on + * represent the shape of the moon, waxing from the bottom right and waning at + * the top left. A small crescent at the bottom right will grow into a larger + * crescent, then add lines in the center for a quarter and half moon. All + * segments are on during a full moon. Then gradually the segments at the + * bottom right will turn off, until all that remains is a small waning + * crescent at the top left. + * + * All segments turn off during a new moon. + * + * On this screen you may press the Alarm button repeatedly to move forward + * in time: the day of the month at the top right will advance by one day for + * each button press, and both the text and the graphical representation will + * display the moon phase for that day. Try pressing the Alarm button 27 times + * now, just to visualize what the moon will look like over the next month. + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/complication/morsecalc_face.c b/movement/watch_faces/complication/morsecalc_face.c index ca03a1a9..30118e2e 100644 --- a/movement/watch_faces/complication/morsecalc_face.c +++ b/movement/watch_faces/complication/morsecalc_face.c @@ -22,89 +22,6 @@ * SOFTWARE. */ -/* -## Morse-code-based RPN calculator - -The calculator is operated by first composing a **token** in Morse code, then submitting it to the calculator. A token specifies either a calculator operation or a float value. -These two parts of the codebase are totally independent: - - 1. The Morse-code reader (`mc.h`, `mc.c`) - 2. The RPN calculator (`calc.h`, `calc.c`, `calc_fn.h`, `calc_fn.c`, `small_strtod.c`) - -The user interface (`morsecalc_face.h`, `morsecalc_face.c`) lets you talk to the RPN calculator through Morse code. - -## Controls - - - `light` is dash - - `alarm` is dot - - `mode` is "finish character" - - long-press `mode` or submit a blank token to switch faces - - long-press `alarm` to show stack - - long-press `light` to toggle the light - -## Morse code token entry -As you enter `.`s and `-`s, the morse code char you've entered will appear in the top center digit. -At the top right is the # of morse code `.`/`-` you've input so far. The character resets at the 6th `.`/`-`. -Once you have the character you want to enter, push `mode` to enter it. -The character will be appended to the current token, whose 6 trailing chars are shown on the main display. -Once you've typed in the token you want, enter a blank Morse code character and then push `mode`. -This submits it to the calculator. - -Special characters: - - - Backspace is `(` (`-.--.`). - - Clear token input without submitting to calculator is `Start transmission` (`-.-.-`). - -## Writing commands -First the calculator will try to interpret the token as a command/stack operation. -Commands are defined in `calc_dict[]` in `movement/lib/morsecalc/calc_fns.h`. -If the command doesn't appear in the dictionary, the calculator tries to interpret the token as a number. - -## Writing numbers -Numbers are written like floating point strings. -Entering a number pushes it to the top of the stack if there's room. -This can get long, so for convenience numerals can also be written in binary with .- = 01. - - 0 1 2 3 4 5 6 7 8 9 - . - -. -- -.. -.- --. --- -... -..- - e t n m d k g o b x - - - Exponent signs must be entered as "p". - - Decimal place "." can be entered as "h" (code ....) - - Sign "-" can be entered as "Ch digraph" (code ----) - -For example: "4.2e-3" can be entered directly, or as "4h2pC3" - similarly, "0.0042" can also be entered as "eheedn" -Once you submit a number to the watch face, it pushes it to the top of the stack if there's room. - -## Number display -After a command runs, the top of the stack is displayed in this format: - - - Main 4 digits = leading 4 digits - - Last 2 digits = exponent - - Top middle = [Stack location, Sign of number] - - Top right = [Stack exponent, Sign of exponent] - -Blank sign digit means positive. -So for example, the watch face might look like this: - - [ 0 -5] - [4200 03] - -... representing `+4.200e-3` is in stack location 0 (the top) and it's one of five items in the stack. - -## Looking at the stack -To show the top of the stack, push and hold `light`/`alarm` or submit a blank token by pushing `mode` a bunch of times. -To show the N-th stack item (0 through 9): - - - Put in the Morse code for N without pushing the mode button. - - Push and hold `alarm`. - -To show the memory register, use `m` instead of a number. - -To see all the calculator operations and their token aliases, see the `calc_dict[]` struct in `calc_fns.h` -*/ - #include <stdlib.h> #include <string.h> #include <math.h> diff --git a/movement/watch_faces/complication/morsecalc_face.h b/movement/watch_faces/complication/morsecalc_face.h index 2ee18622..4768cce7 100644 --- a/movement/watch_faces/complication/morsecalc_face.h +++ b/movement/watch_faces/complication/morsecalc_face.h @@ -25,6 +25,96 @@ #ifndef MORSECALC_FACE_H_ #define MORSECALC_FACE_H_ +/* + * MORSECALC face + * Morse-code-based RPN calculator + * + * The calculator is operated by first composing a **token** in Morse code, + * then submitting it to the calculator. A token specifies either a calculator + * operation or a float value. + * + * These two parts of the codebase are totally independent: + * 1. The Morse-code reader (`mc.h`, `mc.c`) + * 2. The RPN calculator (`calc.h`, `calc.c`, `calc_fn.h`, `calc_fn.c`, `small_strtod.c`) + * + * The user interface (`morsecalc_face.h`, `morsecalc_face.c`) lets you talk + * to the RPN calculator through Morse code. + * + * ## Controls + * - `light` is dash + * - `alarm` is dot + * - `mode` is "finish character" + * - long-press `mode` or submit a blank token to switch faces + * - long-press `alarm` to show stack + * - long-press `light` to toggle the light + * + * ## Morse code token entry + * As you enter `.`s and `-`s, the morse code char you've entered will + * appear in the top center digit. At the top right is the # of morse code + * `.`/`-` you've input so far. The character resets at the 6th `.`/`-`. + * + * Once you have the character you want to enter, push `mode` to enter it. + * + * The character will be appended to the current token, whose 6 trailing + * chars are shown on the main display. Once you've typed in the token you + * want, enter a blank Morse code character and then push `mode`. + * This submits it to the calculator. + * + * Special characters: + * - Backspace is `(` (`-.--.`). + * - Clear token input without submitting to calculator is `Start + * transmission` (`-.-.-`). + * + * ## Writing commands + * First the calculator will try to interpret the token as a command/stack operation. + * Commands are defined in `calc_dict[]` in `movement/lib/morsecalc/calc_fns.h`. + * If the command doesn't appear in the dictionary, the calculator tries to interpret the token as a number. + * + * ## Writing numbers + * Numbers are written like floating point strings. + * Entering a number pushes it to the top of the stack if there's room. + * This can get long, so for convenience numerals can also be written in binary with .- = 01. + * + * 0 1 2 3 4 5 6 7 8 9 + * . - -. -- -.. -.- --. --- -... -..- + * e t n m d k g o b x + * + * - Exponent signs must be entered as "p". + * - Decimal place "." can be entered as "h" (code ....) + * - Sign "-" can be entered as "Ch digraph" (code ----) + * + * For example: "4.2e-3" can be entered directly, or as "4h2pC3" + * similarly, "0.0042" can also be entered as "eheedn" + * Once you submit a number to the watch face, it pushes it to the top of the stack if there's room. + * + * ## Number display + * After a command runs, the top of the stack is displayed in this format: + * + * - Main 4 digits = leading 4 digits + * - Last 2 digits = exponent + * - Top middle = [Stack location, Sign of number] + * - Top right = [Stack exponent, Sign of exponent] + * + * Blank sign digit means positive. + * So for example, the watch face might look like this: + * + * [ 0 -5] + * [4200 03] + * + * ... representing `+4.200e-3` is in stack location 0 (the top) and it's one of five items in the stack. + * + * ## Looking at the stack + * To show the top of the stack, push and hold `light`/`alarm` or submit a blank token by pushing `mode` a bunch of times. + * To show the N-th stack item (0 through 9): + * + * - Put in the Morse code for N without pushing the mode button. + * - Push and hold `alarm`. + * + * To show the memory register, use `m` instead of a number. + * + * To see all the calculator operations and their token aliases, see the `calc_dict[]` struct in `calc_fns.h` + */ + #define MORSECALC_TOKEN_LEN 32 #define MORSECODE_LEN 5 @@ -34,7 +124,7 @@ /* * MC International Morse Code binary tree * Levels of the tree are concatenated. - * '.' = 0 and '-' = 1. + * '.' = 0 and '-' = 1. * * Capitals denote special characters: * C = Ch digraph diff --git a/movement/watch_faces/complication/orrery_face.c b/movement/watch_faces/complication/orrery_face.c index b533960c..42fdf81f 100644 --- a/movement/watch_faces/complication/orrery_face.c +++ b/movement/watch_faces/complication/orrery_face.c @@ -20,7 +20,6 @@ * 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> diff --git a/movement/watch_faces/complication/orrery_face.h b/movement/watch_faces/complication/orrery_face.h index 98060d97..a416afd0 100644 --- a/movement/watch_faces/complication/orrery_face.h +++ b/movement/watch_faces/complication/orrery_face.h @@ -25,6 +25,48 @@ #ifndef ORRERY_FACE_H_ #define ORRERY_FACE_H_ +/* + * ORRERY face + * + * The Orrery watch face is similar to the Astronomy watch face in that it + * calculates properties of the planets, but instead of calculating their + * positions in the sky, this watch face calculates their absolute locations + * in the solar system. This is only useful if you want to plot the planets + * on graph paper, but hey, you never know! + * + * The controls are identical to the Astronomy watch face: while the title + * screen (“Orrery”) is displayed, you can advance through the available + * planets with repeated short presses on the Alarm button. The available + * planets: + * + * ME - Mercury + * VE - Venus + * EA - Earth + * LU - Luna, the Earth’s moon + * MA - Mars + * JU - Jupiter + * SA - Saturn + * UR - Uranus + * NE - Neptune + * + * Note that the sun is not available in this menu, as the sun is always at + * (0,0,0) in this calculation. + * + * Long press on the Alarm button to calculate the planet’s location, and + * after a flashing “C” (for Calculating), you will be presented with the + * planet’s X coordinate in astronomical units. Short press Alarm to cycle + * through the X, Y and Z coordinates, and then long press Alarm to return + * to planet selection. + * + * The large numbers represent the whole number part, and the two smaller + * numbers (in the seconds place) represent the decimal portion. So if you + * see “SA X 736” and “SA Y -662”, you can read that as an X coordinate of + * 7.36 AU and a Y coordinate of -6.62 AU. You can literally draw a dot at + * (0, 0) to represent the sun, and a dot at (7.36, -6.62) to represent + * Saturn. (The Z coordinates tend to be pretty close to zero, as the + * planets largely orbit on a single plane, the ecliptic.) + */ + #include "movement.h" typedef enum { diff --git a/movement/watch_faces/complication/planetary_hours_face.h b/movement/watch_faces/complication/planetary_hours_face.h index 53237df2..dfa6801e 100644 --- a/movement/watch_faces/complication/planetary_hours_face.h +++ b/movement/watch_faces/complication/planetary_hours_face.h @@ -26,12 +26,11 @@ #ifndef planetary_hours_face_H_ #define planetary_hours_face_H_ -#include "movement.h" -#include "sunrise_sunset_face.h" - /* - * BACKGROUND - + * PLANETARY HOURS face + * + * Background + * * Both the 24 hour day and the order of our weekdays have quite esoteric roots. * The ancient Egyptians divided the day up into 12 hours of sunlight and 12 hours * of night time. Obviously the length of these hours varied throughout the year. @@ -74,6 +73,9 @@ * watch face to work properly!) */ +#include "movement.h" +#include "sunrise_sunset_face.h" + typedef struct { // Anything you need to keep track of, put it here! uint32_t planetary_hours[24]; diff --git a/movement/watch_faces/complication/planetary_time_face.h b/movement/watch_faces/complication/planetary_time_face.h index 0ecc11af..b7e8e807 100644 --- a/movement/watch_faces/complication/planetary_time_face.h +++ b/movement/watch_faces/complication/planetary_time_face.h @@ -26,12 +26,11 @@ #ifndef planetary_time_face_H_ #define planetary_time_face_H_ -#include "movement.h" -#include "sunrise_sunset_face.h" - /* + * PLANETARY TIME face + * * BACKGROUND - + * * Both the 24 hour day and the order of our weekdays have quite esoteric roots. * The ancient Egyptians divided the day up into 12 hours of sunlight and 12 hours * of night time. Obviously the length of these hours varied throughout the year. @@ -77,6 +76,9 @@ * watch face to work properly!) */ +#include "movement.h" +#include "sunrise_sunset_face.h" + typedef struct { // Anything you need to keep track of, put it here! uint32_t phase_start; diff --git a/movement/watch_faces/complication/probability_face.h b/movement/watch_faces/complication/probability_face.h index c6d3638f..b2530640 100644 --- a/movement/watch_faces/complication/probability_face.h +++ b/movement/watch_faces/complication/probability_face.h @@ -25,6 +25,18 @@ #ifndef PROBABILITY_FACE_H_ #define PROBABILITY_FACE_H_ +/* + * PROBABILITY face + * + * This face is a dice-rolling random number generator. + * Supports dice with 2, 4, 6, 8, 10, 12, 20, or 100 sides. + * + * Press LIGHT to cycle through die type. + * The current die size is indicated on the left ("C" for 100) + * + * Press ALARM to roll the selected die. + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/complication/pulsometer_face.h b/movement/watch_faces/complication/pulsometer_face.h index 600201e9..288b62c4 100644 --- a/movement/watch_faces/complication/pulsometer_face.h +++ b/movement/watch_faces/complication/pulsometer_face.h @@ -25,6 +25,33 @@ #ifndef PULSOMETER_FACE_H_ #define PULSOMETER_FACE_H_ +/* + * PULSOMETER face + * + * The Pulsometer is an implementation of a sort of a classic mechanical + * watch complication. A classic pulsometer complication involves a + * chronograph with a scale calibrated for counting a certain number of + * heartbeats (often 30). You start it and begin counting heartbeats, and + * stop it after counting the specified number of beats. Once stopped, + * the needle will point to your heart rate. + * + * The pulsometer on Sensor Watch flashes its instructions at launch: + * “Hold Alarm + count 30 beats.” Using the hand on the side where you wear + * your watch, touch your carotid artery (in your neck) and feel for your + * pulse. Once you find it, use your other hand to press and hold the Alarm + * button, and count your heartbeats. When you reach 30 beats, release the + * Alarm button. The display will show a number such as “60 bpm”; this is + * your heart rate in beats per minute. + * + * Two notes: + * o For the first few seconds of a measurement, the display will read “Hi”. + * This indicates that it’s too early for the measured value to be a valid + * heart rate. Once the measurement is below 240 bpm, the display will update. + * o If you hold the button down for more than 45 seconds, the display will + * read “Lo”. If it took this long for you to count 30 heartbeats, this + * indicates that your heart rate is below 40 beats per minute. + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/complication/randonaut_face.h b/movement/watch_faces/complication/randonaut_face.h index fabde798..2f18d45a 100644 --- a/movement/watch_faces/complication/randonaut_face.h +++ b/movement/watch_faces/complication/randonaut_face.h @@ -25,11 +25,8 @@ #ifndef RANDONAUT_FACE_H_ #define RANDONAUT_FACE_H_ -#include "movement.h" -#include "place_face.h" - /* - * RANDONAUT FACE + * RANDONAUT face * ============== * * Randonauting is a way to turn the world around you into an adventure and get the user outside @@ -71,6 +68,9 @@ * */ +#include "movement.h" +#include "place_face.h" + typedef struct { uint8_t mode :3; uint8_t location_format :3; diff --git a/movement/watch_faces/complication/ratemeter_face.h b/movement/watch_faces/complication/ratemeter_face.h index a1f18499..4b10c0f9 100644 --- a/movement/watch_faces/complication/ratemeter_face.h +++ b/movement/watch_faces/complication/ratemeter_face.h @@ -25,6 +25,16 @@ #ifndef RATEMETER_FACE_H_ #define RATEMETER_FACE_H_ +/* + * RATE METER face + * + * The rate meter shows the rate per minute at which the ALARM button is + * being pressed. This is particularly useful in sports where cadence + * tracking is useful. For instance, rowing coaches often use a dedicated + * rate meter - clicking the rate button each time the crew puts their oars + * in the water to see the rate (strokes per minute) on the rate meter. + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/complication/rpn_calculator_alt_face.c b/movement/watch_faces/complication/rpn_calculator_alt_face.c index e48efe83..4a191b7e 100644 --- a/movement/watch_faces/complication/rpn_calculator_alt_face.c +++ b/movement/watch_faces/complication/rpn_calculator_alt_face.c @@ -22,39 +22,6 @@ * SOFTWARE. */ -/* RPN Calculator alternate face. - * - * Operations appear in the 'day' section; ALARM changes between operations when operation is flashing. - * LIGHT executes current operation. - * - * This is the alternate face because it has a non-traditional number entry system which - * I call 'guess a number'. In number entry mode, the watch tries to guess which number you - * want, and you respond with 'smaller' (left - MODE) or larger (right - ALARM). This means - * that when you _are_ entering a number, MODE will no longer move between faces! - * - * Example of entering the number 27 - * - select the NO operation (probably unnecessary, as this is the default), - * and execute it by hitting LIGHT. - * - you are now in number entry mode; you know this because nothing is flashing. - * - Watch displays 10; you hit ALARM to say you want a larger number. - * - Watch displays 100; you hit MODE to say you want a smaller number. - * - Continuing: 50 -> MODE -> 30 -> MODE -> 20 -> ALARM -> 27 - * - Hit LIGHT to add the number to the stack (and now 'NO' is flashing - * again, indicating you're back in operation selection mode). - * - * One other thing to watch out for is how quickly it will switch into scientific notation - * due to the limitations of the display when you have large numbers or non-integer values. - * In this mode, the 'colon' serves at the decimal point, and the numbers in the top right - * are the exponent. - * - * As with the main movement firmware, this has the concept of 'secondary' functions which - * you can jump to by a long hold of ALARM on NO. These are functions to do with stack - * manipulation (pop, swap, dupe, clear, size (le)). If you're _not_ on NO, a long - * hold will take you back to it. - * - * See 'functions' below for names of all operations. - */ - #include <stdlib.h> #include <string.h> #include <math.h> diff --git a/movement/watch_faces/complication/rpn_calculator_alt_face.h b/movement/watch_faces/complication/rpn_calculator_alt_face.h index 2a964675..bb4fd7d0 100644 --- a/movement/watch_faces/complication/rpn_calculator_alt_face.h +++ b/movement/watch_faces/complication/rpn_calculator_alt_face.h @@ -25,6 +25,40 @@ #ifndef CALCULATOR_FACE_H_ #define CALCULATOR_FACE_H_ +/* + * RPN Calculator alternate face. + * + * Operations appear in the 'day' section; ALARM changes between operations when + * operation is flashing. LIGHT executes current operation. + * + * This is the alternate face because it has a non-traditional number entry system which + * I call 'guess a number'. In number entry mode, the watch tries to guess which number you + * want, and you respond with 'smaller' (left - MODE) or larger (right - ALARM). This means + * that when you _are_ entering a number, MODE will no longer move between faces! + * + * Example of entering the number 27 + * - select the NO operation (probably unnecessary, as this is the default), + * and execute it by hitting LIGHT. + * - you are now in number entry mode; you know this because nothing is flashing. + * - Watch displays 10; you hit ALARM to say you want a larger number. + * - Watch displays 100; you hit MODE to say you want a smaller number. + * - Continuing: 50 -> MODE -> 30 -> MODE -> 20 -> ALARM -> 27 + * - Hit LIGHT to add the number to the stack (and now 'NO' is flashing + * again, indicating you're back in operation selection mode). + * + * One other thing to watch out for is how quickly it will switch into scientific notation + * due to the limitations of the display when you have large numbers or non-integer values. + * In this mode, the 'colon' serves at the decimal point, and the numbers in the top right + * are the exponent. + * + * As with the main movement firmware, this has the concept of 'secondary' functions which + * you can jump to by a long hold of ALARM on NO. These are functions to do with stack + * manipulation (pop, swap, dupe, clear, size (le)). If you're _not_ on NO, a long + * hold will take you back to it. + * + * See 'functions' in "rpn_calculator_alt_face.c" for names of all operations. + */ + #include "movement.h" #define CALC_MAX_STACK_SIZE 20 diff --git a/movement/watch_faces/complication/rpn_calculator_face.h b/movement/watch_faces/complication/rpn_calculator_face.h index b47eeea2..57b59a27 100644 --- a/movement/watch_faces/complication/rpn_calculator_face.h +++ b/movement/watch_faces/complication/rpn_calculator_face.h @@ -25,6 +25,15 @@ #ifndef RPN_CALCULATOR_FACE_H_ #define RPN_CALCULATOR_FACE_H_ +/* + * RPN CALCULATOR face + * + * A calculator face using reverse polish notation (RPN). + * + * For usage instructions, please refer to the wiki: + * https://www.sensorwatch.net/docs/watchfaces/complication/#rpn-calculator + */ + #include "movement.h" #define RPN_CALCULATOR_STACK_SIZE 4 diff --git a/movement/watch_faces/complication/sailing_face.c b/movement/watch_faces/complication/sailing_face.c index 748c4f55..a6c13fe8 100644 --- a/movement/watch_faces/complication/sailing_face.c +++ b/movement/watch_faces/complication/sailing_face.c @@ -24,45 +24,12 @@ * SOFTWARE. */ -//----------------------------------------------------------------------------- - #include <stdlib.h> #include <string.h> #include "sailing_face.h" #include "watch.h" #include "watch_utility.h" -/* - -Implements a sailing timer. - -Usage: - -Waiting mode: Light button enters settings, alarm button starts the timer (sailing mode). - -Sailing mode: -Alarm button switches to next programmed start signal, long press on light button -resets timer and enters waiting mode. Countdown to zero, then switch to counting mode. - -Counting mode: -After the start signal (0s), the duration of the race is counted (like a stopwatch timer). -Alarm button increases the lap counter, alarm long press resets lap counter. -Long press on light button resets timer and enters waiting mode. - -Setting mode: -Alarm button increases active (blinking) signal. Goes to 0 if upper boundary -(11 or whatever the signal left to the active one is set to) is met. -10 is printed vertically (letter o plus top segment). -Alarm button long press resets to default minutes (5-4-1-0). -Light button cycles through the signals. -Long press on light button cycles through sound modes: -- Bell indicator: Sound at start (0s) only. -- Signal indicator: Sound at each programmed signal and at start. -- Bell+Signal: Sound at each minute, at 30s and at 10s countdown. -- No indicator: No sound. - -*/ - #define sl_SELECTIONS 6 #define DEFAULT_MINUTES { 5,4,1,0,0,0 } diff --git a/movement/watch_faces/complication/sailing_face.h b/movement/watch_faces/complication/sailing_face.h index 0f9fd9da..5546f275 100644 --- a/movement/watch_faces/complication/sailing_face.h +++ b/movement/watch_faces/complication/sailing_face.h @@ -24,17 +24,43 @@ * SOFTWARE. */ -//----------------------------------------------------------------------------- - #ifndef SAILING_FACE_H_ #define SAILING_FACE_H_ -#include "movement.h" - /* -A sailing sailing/timer face -*/ + * SAILING face + * Implements a sailing timer. + * + * Usage: + * + * Waiting mode: + * LIGHT button enters settings + * ALARM button starts the timer (sailing mode). + * + * Sailing mode: + * ALARM button switches to next programmed start signal. + * Long press on LIGHT button resets timer and enters waiting mode. + * Countdown to zero, then switch to counting mode. + * + * Counting mode: + * After the start signal (0s), the duration of the race is counted (like a stopwatch timer). + * ALARM button increases the lap counter, ALARM long press resets lap counter. + * Long press on LIGHT button resets timer and enters waiting mode. + * + * Setting mode: + * ALARM button increases active (blinking) signal. Goes to 0 if upper boundary + * (11 or whatever the signal left to the active one is set to) is met. + * 10 is printed vertically (letter o plus top segment). + * ALARM button long press resets to default minutes (5-4-1-0). + * LIGHT button cycles through the signals. + * Long press on LIGHT button cycles through sound modes: + * - Bell indicator: Sound at start (0s) only. + * - Signal indicator: Sound at each programmed signal and at start. + * - Bell+Signal: Sound at each minute, at 30s and at 10s countdown. + * - No indicator: No sound. + */ +#include "movement.h" typedef enum { sl_waiting, diff --git a/movement/watch_faces/complication/ships_bell_face.h b/movement/watch_faces/complication/ships_bell_face.h index dd377317..3a04b938 100644 --- a/movement/watch_faces/complication/ships_bell_face.h +++ b/movement/watch_faces/complication/ships_bell_face.h @@ -25,9 +25,8 @@ #ifndef SHIPS_BELL_FACE_H_ #define SHIPS_BELL_FACE_H_ -#include "movement.h" - /* + * SHIP'S BELL face * A ship's bell complication. * * See: https://en.wikipedia.org/wiki/Ship%27s_bell#Simpler_system @@ -45,6 +44,8 @@ * - long press Alarm button: Cycle through the watches (All/1/2/3) */ +#include "movement.h" + typedef struct { bool bell_enabled; uint8_t on_watch; diff --git a/movement/watch_faces/complication/stock_stopwatch_face.h b/movement/watch_faces/complication/stock_stopwatch_face.h index d8880df7..6796a849 100644 --- a/movement/watch_faces/complication/stock_stopwatch_face.h +++ b/movement/watch_faces/complication/stock_stopwatch_face.h @@ -25,12 +25,34 @@ #ifndef STOCK_STOPWATCH_FACE_H_ #define STOCK_STOPWATCH_FACE_H_ -#include "movement.h" +/* + * STOCK STOPWATCH face + * + * The Stock Stopwatch face implements the original F-91W stopwatch + * functionality, including counting hundredths of seconds and lap timing. + * + * Use the ALARM button to start and stop the stopwatch. + * Press the LIGHT button while the stopwatch is running to view the lap time. + * (The stopwatch continues running in the background, indicated by a blinking colon.) + * Press the LIGHT button again to switch back to the running stopwatch. + * Press the LIGHT button when the timekeeping is stopped to reset the stopwatch. + * + * There are two improvements compared to the original F-91W: + * o When the stopwatch reaches 59:59, the counter does not simply jump back + * to zero but keeps track of hours in the upper right-hand corner + * (up to 24 hours). + * o Long-press the light button to toggle the LED behavior. + * It either turns on with each button press or remains off. + * + * NOTE: + * This watch face relies heavily on static vars in stock_stopwatch.c. + * The disadvantage is that you cannot use more than one instance of this + * watch face on your custom firmware - but then again, who would want that? + * The advantage is that accessing vars is more direct and faster, and we + * can save some precious cpu cycles. :-) + */ -// This watch face relies heavily on static vars in stock_stopwatch.c. -// The disadvantage is that you cannot use more than one instance of this watch face on -// your custom firmware - but then again, who would want that? The advantage is that accessing -// vars is more direct and faster, and we can save some precious cpu cycles :-) +#include "movement.h" typedef struct { bool light_on_button; // determines whether the light button actually triggers the led diff --git a/movement/watch_faces/complication/stopwatch_face.h b/movement/watch_faces/complication/stopwatch_face.h index a30c7fb6..7bdf19c6 100644 --- a/movement/watch_faces/complication/stopwatch_face.h +++ b/movement/watch_faces/complication/stopwatch_face.h @@ -26,6 +26,17 @@ #ifndef STOPWATCH_FACE_H_ #define STOPWATCH_FACE_H_ +/* + * STOPWATCH FACE + * + * The Stopwatch face provides basic stopwatch functionality: you can start + * and stop the stopwatch with the alarm button. Pressing the light button + * when the timer is stopped resets it. + * + * This face does not count sub-seconds. + * See also: "stock_stopwatch_face.h" + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/complication/sunrise_sunset_face.h b/movement/watch_faces/complication/sunrise_sunset_face.h index d3f28792..16e65b73 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.h +++ b/movement/watch_faces/complication/sunrise_sunset_face.h @@ -25,10 +25,18 @@ #ifndef SUNRISE_SUNSET_FACE_H_ #define SUNRISE_SUNSET_FACE_H_ -#include "movement.h" +/* + * SUNRISE & SUNSET FACE + * + * The Sunrise/Sunset face is designed to display the next sunrise or sunset + * for a given location. It also functions as an interface for setting the + * location register, which other watch faces can use for various purposes. + * + * Refer to the wiki for usage instructions: + * https://www.sensorwatch.net/docs/watchfaces/complication/#sunrisesunset + */ -// The Sunrise/Sunset face is designed to display the next sunrise or sunset for a given location. -// TODO: It also functions as an interface for setting the location register, which other watch faces can use for various purposes. +#include "movement.h" typedef struct { uint8_t sign: 1; // 0-1 diff --git a/movement/watch_faces/complication/tachymeter_face.h b/movement/watch_faces/complication/tachymeter_face.h index 1889b40e..3b23525f 100644 --- a/movement/watch_faces/complication/tachymeter_face.h +++ b/movement/watch_faces/complication/tachymeter_face.h @@ -25,6 +25,49 @@ #ifndef TACHYMETER_FACE_H_ #define TACHYMETER_FACE_H_ +/* + * TACHYMETER face + * + * The Tachymeter complication emulates the tachymeter function often + * present in watches, that computes the average speed in [units per hour] + * for a given distance given in [units]. + * + * Use case: + * User sets the distance + * User starts the tachymeter when the trip begins + * User stops the tachymeter when the trip ends + * The watch presents the average speed and trip duration in seconds + * + * Usage: + * Go to tachymeter face, TC is shown in the Weekday Digits + * A steady d in the Day Digits indicates the distance to be used. + * To edit the distance: + * Long-press the Alarm button, the distance edition page (d will blink) + * Use the Light button to change the editing (blinking) digit, and press Alarm to increase its value + * Once done, long-press the Alarm button to exit the distance edition page + * Press the Alarm button to start the tachymeter. + * A running animation will appear in the Day Digits + * Press the Alarm button to stop the tachymeter + * The average speed and total time information will alternate. + * The average speed will be shown alongside /h in the Day Digits; + * and the total time will be shown alongside t in the Day Digits. + * Long press the Light button to return to the distance d page, + * and restart the tachymeter from there. + * Long-press the light button in the steady distance page to reset + * the distance to 1.00 + * + * Pending design points + * o movement_request_tick_frequency(4) is used to obtain a 4Hz ticking, thus + * having a time resolution of 250 ms. Not sure if using event.subsecond` + * is the proper way to get the fractions of second for the start and + * final times. + * o For distance and average speed, the Second Digits (position 8 and 9) + * can be seen as decimals, thus possible to show distances as short as + * 0.01 km (or miles) and speeds as low as 0.01 km/h (or mph). However, + * if the same idea is used for the total time (showing hundredths), + * this limits the display to 9999.99 seconds (~2h:45m). + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/complication/tally_face.h b/movement/watch_faces/complication/tally_face.h index 6584e87d..8096592a 100644 --- a/movement/watch_faces/complication/tally_face.h +++ b/movement/watch_faces/complication/tally_face.h @@ -25,11 +25,17 @@ #ifndef TALLY_FACE_H_ #define TALLY_FACE_H_ -#include "movement.h" +/* + * TALLY face + * + * Tally face is designed to act as a tally counter. + * Based on the counter_face watch face by Shogo Okamoto. + * + * To advance the counter, press the ALARM button. + * To reset, long press the ALARM button. + */ -// Tally face is designed to act as a tally counter. -// Based on the counter_face watch face by Shogo Okamoto. -// To advance the counter, press the Alarm button. To reset, long press the Alarm button. +#include "movement.h" typedef struct { uint32_t tally_idx; diff --git a/movement/watch_faces/complication/tarot_face.h b/movement/watch_faces/complication/tarot_face.h index 9dfe8b3d..b48a7ae9 100644 --- a/movement/watch_faces/complication/tarot_face.h +++ b/movement/watch_faces/complication/tarot_face.h @@ -25,10 +25,8 @@ #ifndef TAROT_FACE_H_ #define TAROT_FACE_H_ -#include "movement.h" - /* - * Tarot card watch face + * TAROT CARD watch face * * Draw from a deck of tarot cards. Can choose between major arcana only or * entire deck. @@ -62,6 +60,8 @@ * - Light button (long press): go back to Draw screen, for choosing different draw parameters. */ +#include "movement.h" + #define MAX_CARDS_TO_DRAW 10 typedef struct { diff --git a/movement/watch_faces/complication/tempchart_face.c b/movement/watch_faces/complication/tempchart_face.c index 53b027d9..6c6d1bd9 100644 --- a/movement/watch_faces/complication/tempchart_face.c +++ b/movement/watch_faces/complication/tempchart_face.c @@ -20,11 +20,6 @@ * 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. - * - * Gathers temperature statistics in a chart form. Statistics bins are per hour / per 0.5°C. - * Saved to file every day at 00:00. Can help improve watch precision in the future. - * If you can gather statistics over few months, and then send tempchart.ini to 3@14.by - it - * will help future generations of precision quartz watches. */ #include <stdlib.h> diff --git a/movement/watch_faces/complication/tempchart_face.h b/movement/watch_faces/complication/tempchart_face.h index ce870c8f..3c9a389f 100644 --- a/movement/watch_faces/complication/tempchart_face.h +++ b/movement/watch_faces/complication/tempchart_face.h @@ -25,6 +25,19 @@ #ifndef TEMPCHART_FACE_H_ #define TEMPCHART_FACE_H_ +/* + * TEMPERATURE CHART face + * + * Gathers temperature statistics in a chart form. + * Statistics bins are per hour / per 0.5°C. + * + * Saved to file every day at 00:00. + * Can help improve watch precision in the future. + * + * If you can gather statistics over few months, and then send "tempchart.ini" + * to "3@14.by", it will help future generations of precision quartz watches. + */ + #include "movement.h" void tempchart_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/movement/watch_faces/complication/time_left_face.h b/movement/watch_faces/complication/time_left_face.h index 0ed7fd28..805fd198 100644 --- a/movement/watch_faces/complication/time_left_face.h +++ b/movement/watch_faces/complication/time_left_face.h @@ -25,9 +25,9 @@ #ifndef TIME_LEFT_FACE_H_ #define TIME_LEFT_FACE_H_ -#include "movement.h" - /* + * TIME LEFT face + * * The Time Left Face helps you to visualize how far you have proceeded in a certain * time span. Much like the Day One Face, you can set your beginning date. In addition * to that, you also set your target or destination date. You can then use the face @@ -65,6 +65,8 @@ * */ +#include "movement.h" + typedef struct { uint8_t current_page; uint16_t current_year; diff --git a/movement/watch_faces/complication/timer_face.c b/movement/watch_faces/complication/timer_face.c index 70f250a5..29392d69 100644 --- a/movement/watch_faces/complication/timer_face.c +++ b/movement/watch_faces/complication/timer_face.c @@ -22,15 +22,13 @@ * 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 +static const uint32_t _default_timer_values[] = {0x000200, 0x000500, 0x000A00, 0x001400, 0x002D02}; // 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}; @@ -199,7 +197,7 @@ void timer_face_setup(movement_settings_t *settings, uint8_t watch_face_index, v timer_state_t *state = (timer_state_t *)*context_ptr; memset(*context_ptr, 0, sizeof(timer_state_t)); state->watch_face_index = watch_face_index; - for (uint8_t i = 0; i < sizeof(_default_timer_values) / sizeof(uint16_t); i++) { + for (uint8_t i = 0; i < sizeof(_default_timer_values) / sizeof(uint32_t); i++) { state->timers[i].value = _default_timer_values[i]; } } diff --git a/movement/watch_faces/complication/timer_face.h b/movement/watch_faces/complication/timer_face.h index 5f035cb3..3302f125 100644 --- a/movement/watch_faces/complication/timer_face.h +++ b/movement/watch_faces/complication/timer_face.h @@ -22,14 +22,11 @@ * SOFTWARE. */ -//----------------------------------------------------------------------------- - #ifndef TIMER_FACE_H_ #define TIMER_FACE_H_ -#include "movement.h" - /* + * TIMER face * Advanced timer/countdown face with pre-set timer lengths * * This watch face provides the functionality of starting a countdown by choosing @@ -53,6 +50,8 @@ * */ +#include "movement.h" + #define TIMER_SLOTS 9 // offer 9 timer slots typedef enum { diff --git a/movement/watch_faces/complication/tomato_face.h b/movement/watch_faces/complication/tomato_face.h index 5404ad11..33a086c6 100644 --- a/movement/watch_faces/complication/tomato_face.h +++ b/movement/watch_faces/complication/tomato_face.h @@ -25,6 +25,26 @@ #ifndef TOMATO_FACE_H_ #define TOMATO_FACE_H_ +/* + * TOMATO TIMER face + * + * Add a "tomato" timer watch face that alternates between 25 and 5 minute + * timers as in the Pomodoro Technique. + * https://en.wikipedia.org/wiki/Pomodoro_Technique + * + * The top right letter shows mode (f for focus or b for break). + * The bottom right shows how many focus sessions you've completed. + * (You can reset the count with a long press of alarm) + * + * When you show up and it says 25 minutes, you can start it (alarm), + * switch to 5 minute (light) mode or leave (mode). + * + * When it's running you can reset (alarm), or leave (mode). + * + * When it's done, we beep and go back to step 1, changing switching + * mode from focus to break (or break to focus) + */ + #include "movement.h" typedef enum { diff --git a/movement/watch_faces/complication/toss_up_face.h b/movement/watch_faces/complication/toss_up_face.h index ca6136a7..cff34096 100644 --- a/movement/watch_faces/complication/toss_up_face.h +++ b/movement/watch_faces/complication/toss_up_face.h @@ -25,10 +25,8 @@ #ifndef TOSS_UP_FACE_H_ #define TOSS_UP_FACE_H_ -#include "movement.h" - /* - * TOSS UP FACE + * TOSS UP face * ============ * * Playful watch face for games of chance or divination using coins or dice. @@ -75,6 +73,8 @@ * */ +#include "movement.h" + typedef struct { // Anything you need to keep track of, put it here! uint32_t entropy; diff --git a/movement/watch_faces/complication/totp_face.c b/movement/watch_faces/complication/totp_face.c index a6550187..242820d7 100644 --- a/movement/watch_faces/complication/totp_face.c +++ b/movement/watch_faces/complication/totp_face.c @@ -1,3 +1,27 @@ +/* + * MIT License + * + * Copyright (c) 2022 Wesley Ellis (https://github.com/tahnok) + * + * 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 "totp_face.h" @@ -5,15 +29,6 @@ #include "watch_utility.h" #include "TOTP.h" -// Use https://cryptii.com/pipes/base32-to-hex to convert base32 to hex -// Use https://github.com/susam/mintotp to generate test codes for verification -// Available algorothms: -// SHA1 (most TOTP codes use this) -// SHA224 -// SHA256 -// SHA384 -// SHA512 - //////////////////////////////////////////////////////////////////////////////// // Enter your TOTP key data below static const uint8_t num_keys = 2; diff --git a/movement/watch_faces/complication/totp_face.h b/movement/watch_faces/complication/totp_face.h index cff51919..1248f716 100644 --- a/movement/watch_faces/complication/totp_face.h +++ b/movement/watch_faces/complication/totp_face.h @@ -1,6 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2022 Wesley Ellis (https://github.com/tahnok) + * + * 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 TOTP_FACE_H_ #define TOTP_FACE_H_ +/* + * TOTP face + * Time-based one-time password (TOTP) generator + * + * Generate one-time passwords often used for two-factor authentication. + * The secret key must be set by hand, by editing "totp_face.c". + * + * Available algorithms: + * o SHA1 (most TOTP codes use this) + * o SHA224 + * o SHA256 + * o SHA384 + * o SHA512 + * + * Instructions: + * o Find your secret key(s) and convert them to the required format. + * o Use https://cryptii.com/pipes/base32-to-hex to convert base32 to hex + * o Use https://github.com/susam/mintotp to generate test codes for verification + * o Edit global variables in "totp_face.c" to configure your stored keys: + * o "keys", "key_sizes", "timesteps", and "algorithms" set the + * cryptographic parameters for each secret key. + * o "labels" sets the two-letter label for each key + * (This replaces the day-of-week indicator) + * o Once finished, remove the two provided examples. + * + * If you have more than one secret key, press ALARM to cycle through them. + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/complication/totp_face_lfs.c b/movement/watch_faces/complication/totp_face_lfs.c index d52d8629..4066ac48 100644 --- a/movement/watch_faces/complication/totp_face_lfs.c +++ b/movement/watch_faces/complication/totp_face_lfs.c @@ -1,3 +1,27 @@ +/* + * MIT License + * + * Copyright (c) 2022 Wesley Ellis (https://github.com/tahnok) + * + * 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 <math.h> @@ -11,24 +35,6 @@ #include "totp_face_lfs.h" -/* Reads from a file totp_uris.txt where each line is what's in a QR code: - * e.g. - * otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example - * otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30 - * This is also the same as what Aegis exports in plain-text format. - * - * Minimal sanitisation of input, however. - * - * At the moment, to get the records onto the filesystem, start a serial connection and do: - * echo otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example > totp_uris.txt - * echo otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30 >> totp_uris.txt - * (note the double >> in the second one) - * - * You may want to customise the characters that appear to identify the 2FA code. These are just the first two characters of the issuer, - * and it's fine to modify the URI. - */ - - #define MAX_TOTP_RECORDS 20 #define MAX_TOTP_SECRET_SIZE 48 #define TOTP_FILE "totp_uris.txt" diff --git a/movement/watch_faces/complication/totp_face_lfs.h b/movement/watch_faces/complication/totp_face_lfs.h index 0f388bc3..64c6ce15 100644 --- a/movement/watch_faces/complication/totp_face_lfs.h +++ b/movement/watch_faces/complication/totp_face_lfs.h @@ -1,6 +1,54 @@ +/* + * MIT License + * + * Copyright (c) 2022 Wesley Ellis (https://github.com/tahnok) + * + * 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 TOTP_FACE_LFS_H_ #define TOTP_FACE_LFS_H_ +/* + * TOTP-LFS face + * Time-based one-time password (TOTP) generator using LFS + * + * Reads from a file "totp_uris.txt", containing a single secret key in a + * series of URLs. Each line is what's in a QR code, e.g.: + * otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example + * otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30 + * + * This is also the same as what Aegis exports in plain-text format. + * This face performs minimal sanitisation of input, however. + * + * At the moment, to get the records onto the filesystem, start a serial connection and do: + * echo otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example > totp_uris.txt + * echo otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30 >> totp_uris.txt + * (note the double >> in the second one) + * + * You may want to customise the characters that appear to identify the 2FA + * code. These are just the first two characters of the issuer, and it's fine + * to modify the URI. + * + * If you have more than one secret key, press ALARM to cycle through them. + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/complication/tuning_tones_face.c b/movement/watch_faces/complication/tuning_tones_face.c new file mode 100644 index 00000000..a139427a --- /dev/null +++ b/movement/watch_faces/complication/tuning_tones_face.c @@ -0,0 +1,140 @@ +/* + * MIT License + * + * Copyright (c) 2023 Per Waagø + * + * 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 "tuning_tones_face.h" + +/* + + This face plays a tone that can be used as a reference when tuning + musical instrument. + + - The alarm button (short press) starts and stops the tone + - The light button (short press) changes which note is played. The name + of the note is shown in the display. + +*/ + +typedef struct Note { + BuzzerNote note; + char * name; +} Note; + +static Note notes[] = { + { .note = BUZZER_NOTE_C5, .name = "C " }, + { .note = BUZZER_NOTE_C5SHARP_D5FLAT, .name = "Db" }, + { .note = BUZZER_NOTE_D5, .name = "D " }, + { .note = BUZZER_NOTE_D5SHARP_E5FLAT, .name = "Eb" }, + { .note = BUZZER_NOTE_E5, .name = "E " }, + { .note = BUZZER_NOTE_F5, .name = "F " }, + { .note = BUZZER_NOTE_F5SHARP_G5FLAT, .name = "Gb" }, + { .note = BUZZER_NOTE_G5, .name = "G " }, + { .note = BUZZER_NOTE_G5SHARP_A5FLAT, .name = "Ab" }, + { .note = BUZZER_NOTE_A5, .name = "A " }, + { .note = BUZZER_NOTE_A5SHARP_B5FLAT, .name = "Bb" }, + { .note = BUZZER_NOTE_B5, .name = "B " }, +}; + +static size_t note_count = sizeof notes / sizeof *notes; + +static void draw(tuning_tones_state_t *state) +{ + watch_display_string(notes[state->note_ind].name, 8); +} + +void tuning_tones_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + tuning_tones_state_t *state = malloc(sizeof *state); + memset(state, 0, sizeof *state); + state->note_ind = 9; + *context_ptr = state; + } +} + +void tuning_tones_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +static void update_buzzer(const tuning_tones_state_t *state) +{ + if (state->playing) { + watch_set_buzzer_off(); + watch_set_buzzer_period(NotePeriods[notes[state->note_ind].note]); + watch_set_buzzer_on(); + } +} + +bool tuning_tones_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + tuning_tones_state_t *state = (tuning_tones_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + draw(state); + break; + case EVENT_TICK: + break; + case EVENT_LIGHT_BUTTON_DOWN: + state->note_ind++; + if (state->note_ind == note_count) { + state->note_ind = 0; + } + update_buzzer(state); + draw(state); + break; + case EVENT_LIGHT_BUTTON_UP: + break; + case EVENT_ALARM_BUTTON_DOWN: + state->playing = !state->playing; + if (!state->playing) { + watch_set_buzzer_off(); + } else { + update_buzzer(state); + } + case EVENT_ALARM_BUTTON_UP: + break; + case EVENT_TIMEOUT: + movement_move_to_face(0); + break; + case EVENT_LOW_ENERGY_UPDATE: + break; + default: + return movement_default_loop_handler(event, settings); + } + + return !state->playing; +} + +void tuning_tones_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + tuning_tones_state_t *state = (tuning_tones_state_t *)context; + + if (state->playing) { + state->playing = false; + watch_set_buzzer_off(); + } +} diff --git a/movement/watch_faces/complication/tuning_tones_face.h b/movement/watch_faces/complication/tuning_tones_face.h new file mode 100644 index 00000000..d6e3495e --- /dev/null +++ b/movement/watch_faces/complication/tuning_tones_face.h @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2023 Per Waagø + * + * 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 TUNING_TONES_FACE_H_ +#define TUNING_TONES_FACE_H_ + +#include "movement.h" + +/* + * A DESCRIPTION OF YOUR WATCH FACE + * + * and a description of how use it + * + */ + +typedef struct { + // Anything you need to keep track of, put it here! + bool playing; + size_t note_ind; +} tuning_tones_state_t; + +void tuning_tones_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void tuning_tones_face_activate(movement_settings_t *settings, void *context); +bool tuning_tones_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void tuning_tones_face_resign(movement_settings_t *settings, void *context); + +#define tuning_tones_face ((const watch_face_t){ \ + tuning_tones_face_setup, \ + tuning_tones_face_activate, \ + tuning_tones_face_loop, \ + tuning_tones_face_resign, \ + NULL, \ +}) + +#endif // TUNING_TONES_FACE_H_ + diff --git a/movement/watch_faces/complication/wake_face.c b/movement/watch_faces/complication/wake_face.c index 5c5f86be..6fa801fa 100644 --- a/movement/watch_faces/complication/wake_face.c +++ b/movement/watch_faces/complication/wake_face.c @@ -22,24 +22,12 @@ * SOFTWARE. */ -//----------------------------------------------------------------------------- - #include <stdlib.h> #include <string.h> -// #include <threads.h> - #include "wake_face.h" #include "watch.h" #include "watch_utility.h" -/* - UI Notes - º Light advances hour by 1 - º Light long press advances hour by 6 - º Alarm advances minute by 10 - º Alarm long press cycles through signal modes (just one at the moment) -*/ - // // Private // diff --git a/movement/watch_faces/complication/wake_face.h b/movement/watch_faces/complication/wake_face.h index c091c8f3..b4a25a9c 100644 --- a/movement/watch_faces/complication/wake_face.h +++ b/movement/watch_faces/complication/wake_face.h @@ -22,11 +22,24 @@ * SOFTWARE. */ -//----------------------------------------------------------------------------- - #ifndef WAKE_FACE_H_ #define WAKE_FACE_H_ +/* + * WAKE daily alarm face + * + * Basic daily alarm clock face. Seems useful if nothing else in the interest + * of feature parity with the F-91W’s OEM module, 593. + * + * Also experiments with caret-free UI: One button cycles hours, the other + * minutes, so there’s no toggling between display and adjust modes and no + * cycling the caret through the UI. + * º LIGHT advances hour by 1 + * º LIGHT long press advances hour by 6 + * º ALARM advances minute by 10 + * º ALARM long press cycles through signal modes (just one at the moment) + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/demo/character_set_face.h b/movement/watch_faces/demo/character_set_face.h index 82627aed..2fac26ad 100644 --- a/movement/watch_faces/demo/character_set_face.h +++ b/movement/watch_faces/demo/character_set_face.h @@ -25,6 +25,17 @@ #ifndef CHARACTER_SET_FACE_H_ #define CHARACTER_SET_FACE_H_ +/* + * CHARACTER SET FACE + * + * This watch face displays all of the characters in the Sensor Watch character + * set. You can advance from one character to the next with a short press of the + * ALARM button. + * + * This watch face may be useful to watch face developers, in that it can help + * them to understand which characters will work in different positions. + */ + #include "movement.h" void character_set_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/movement/watch_faces/demo/chirpy_demo_face.h b/movement/watch_faces/demo/chirpy_demo_face.h index 2d34107a..90a6133b 100644 --- a/movement/watch_faces/demo/chirpy_demo_face.h +++ b/movement/watch_faces/demo/chirpy_demo_face.h @@ -25,8 +25,6 @@ #ifndef CHIRPY_DEMO_FACE_H_ #define CHIRPY_DEMO_FACE_H_ -#include "movement.h" - /* * CHIRPY DEMO FACE * @@ -50,9 +48,10 @@ * * To record and decode a chirpy transmission on your computer, you can use the web app here: * https://jealousmarkup.xyz/off/chirpy/rx/ - * */ +#include "movement.h" + void chirpy_demo_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); void chirpy_demo_face_activate(movement_settings_t *settings, void *context); bool chirpy_demo_face_loop(movement_event_t event, movement_settings_t *settings, void *context); diff --git a/movement/watch_faces/demo/demo_face.h b/movement/watch_faces/demo/demo_face.h index 026e0d10..669f3872 100644 --- a/movement/watch_faces/demo/demo_face.h +++ b/movement/watch_faces/demo/demo_face.h @@ -25,6 +25,17 @@ #ifndef DEMO_FACE_H_ #define DEMO_FACE_H_ +/* + * DEMO FACE + * + * This watch was designed for the Crowd Supply marketing team, so they could + * photograph the various functions of Sensor Watch. The Alarm button advances + * through static screens that simulate different watch faces. + * + * This watch face may only be useful to you if you need to photograph Sensor + * Watch, i.e. for a blog post. + */ + #include "movement.h" void demo_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/movement/watch_faces/demo/frequency_correction_face.h b/movement/watch_faces/demo/frequency_correction_face.h index 52c4e621..0b9c435d 100644 --- a/movement/watch_faces/demo/frequency_correction_face.h +++ b/movement/watch_faces/demo/frequency_correction_face.h @@ -25,6 +25,18 @@ #ifndef FREQUENCY_CORRECTION_FACE_H_ #define FREQUENCY_CORRECTION_FACE_H_ +/* + * FREQUENCY CORRECTION FACE + * + * While active, this face generates a square-wave on pin A1 of the 9-pin + * connector. The output frequency is adjustable from 64 Hz to 0.5 Hz. + * Long-press ALARM to cycle through available frequencies. + * + * This face also displays the value of the watch's frequency-correction + * register. This setting varies from -127 to +127. Press LIGHT to increment + * or ALARM to decrement the setting. + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/demo/hello_there_face.h b/movement/watch_faces/demo/hello_there_face.h index 1140bb20..dc76f5f5 100644 --- a/movement/watch_faces/demo/hello_there_face.h +++ b/movement/watch_faces/demo/hello_there_face.h @@ -25,6 +25,13 @@ #ifndef HELLO_THERE_FACE_H_ #define HELLO_THERE_FACE_H_ +/* + * HELLO THERE FACE + * + * A simple demo that displays the word "Hello" and then the word "there", + * on an endless loop. Press ALARM to pause or resume the animation. + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/demo/lis2dw_logging_face.h b/movement/watch_faces/demo/lis2dw_logging_face.h index f6ea85e7..ad325184 100644 --- a/movement/watch_faces/demo/lis2dw_logging_face.h +++ b/movement/watch_faces/demo/lis2dw_logging_face.h @@ -25,6 +25,14 @@ #ifndef LIS2DW_LOGGING_FACE_H_ #define LIS2DW_LOGGING_FACE_H_ +/* + * LIS2DW Accelerometer Data Logger + * + * This is an experimental watch face for logging data on the “Sensor Watch + * Motion Express” board. I will add more documentation for this watch face + * once this sensor board is more widely available. + */ + #include "movement.h" #include "watch.h" diff --git a/movement/watch_faces/demo/voltage_face.h b/movement/watch_faces/demo/voltage_face.h index dc5e631b..59d0f9ea 100644 --- a/movement/watch_faces/demo/voltage_face.h +++ b/movement/watch_faces/demo/voltage_face.h @@ -25,6 +25,17 @@ #ifndef VOLTAGE_FACE_H_ #define VOLTAGE_FACE_H_ +/* + * VOLTAGE face + * + * This watch face is very simple and has no controls to speak of. It displays + * the battery voltage as measured by the SAM L22’s ADC. + * + * Note that the Simple Clock watch face includes a low battery warning, so you + * don’t technically need to this watch face unless you want to track the + * battery level. + */ + #include "movement.h" void voltage_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/movement/watch_faces/sensor/accelerometer_data_acquisition_face.h b/movement/watch_faces/sensor/accelerometer_data_acquisition_face.h index 9cea8095..946639da 100644 --- a/movement/watch_faces/sensor/accelerometer_data_acquisition_face.h +++ b/movement/watch_faces/sensor/accelerometer_data_acquisition_face.h @@ -25,6 +25,12 @@ #ifndef ACCELEROMETER_DATA_ACQUISITION_FACE_H_ #define ACCELEROMETER_DATA_ACQUISITION_FACE_H_ +/* + * ACCELEROMETER DATA ACQUISITION + * + * TODO: Add description here, including controls. + */ + #include "movement.h" #define ACCELEROMETER_DATA_ACQUISITION_INVALID ((uint64_t)(0b11)) // all bits are 1 when the flash is erased diff --git a/movement/watch_faces/sensor/lightmeter_face.c b/movement/watch_faces/sensor/lightmeter_face.c index 861e28d8..9fb489a2 100644 --- a/movement/watch_faces/sensor/lightmeter_face.c +++ b/movement/watch_faces/sensor/lightmeter_face.c @@ -22,37 +22,6 @@ * SOFTWARE. */ -/* Aperture-priority Light Meter Face - * - * Tested with the "Q3Q-SWAB-A1-00 Temperature + Test Points + OPT3001" flexboard. - * This flexboard could use a revision: - * - * - The thermistor components should be moved west a mm or flipped to the backside - * to avoid stressing the flexboard against the processor so much. - * - The 'no connect' pad falls off easily. - * - * Controls: - * - * - Trigger a measurement by long-pressing Alarm. - * Sensor integration is happening when the Signal indicator is on. - * - * - ISO setting can be cycled by long-pressing Light. - * During integration the current ISO setting will be displayed. - * - * - EV measurement in the top right: "LAP" indicates "half stop". - * So "LAP -1" means EV = -1.5. Likewise "LAP 13" means EV = +13.5 - * - * - Aperture in the bottom right: the last 3 main digits are the f-stop. - * Adjust this number in half-stop increments using Alarm = +1/2 and Light = -1/2. - * - * - Best shutter speed in the bottom left: the first 3 digits are the shutter speed. - * Some special chars are needed here: "-" = seconds, "h" = extra half second, "K" = thousands. - * "HI" or "LO" if there's no shutter in the dictionary within 0.5 stops of correct exposure. - * - * - Mode long-press changes the main digits to show raw sensor lux measurements. - * - */ - #include <stdlib.h> #include <string.h> #include <math.h> diff --git a/movement/watch_faces/sensor/lightmeter_face.h b/movement/watch_faces/sensor/lightmeter_face.h index 2f8813f4..affae2e4 100644 --- a/movement/watch_faces/sensor/lightmeter_face.h +++ b/movement/watch_faces/sensor/lightmeter_face.h @@ -25,6 +25,37 @@ #ifndef LIGHTMETER_FACE_H_ #define LIGHTMETER_FACE_H_ +/* + * Aperture-priority Light Meter Face + * + * Tested with the "Q3Q-SWAB-A1-00 Temperature + Test Points + OPT3001" flexboard. + * This flexboard could use a revision: + * + * - The thermistor components should be moved west a mm or flipped to the backside + * to avoid stressing the flexboard against the processor so much. + * - The 'no connect' pad falls off easily. + * + * Controls: + * + * - Trigger a measurement by long-pressing Alarm. + * Sensor integration is happening when the Signal indicator is on. + * + * - ISO setting can be cycled by long-pressing Light. + * During integration the current ISO setting will be displayed. + * + * - EV measurement in the top right: "LAP" indicates "half stop". + * So "LAP -1" means EV = -1.5. Likewise "LAP 13" means EV = +13.5 + * + * - Aperture in the bottom right: the last 3 main digits are the f-stop. + * Adjust this number in half-stop increments using Alarm = +1/2 and Light = -1/2. + * + * - Best shutter speed in the bottom left: the first 3 digits are the shutter speed. + * Some special chars are needed here: "-" = seconds, "h" = extra half second, "K" = thousands. + * "HI" or "LO" if there's no shutter in the dictionary within 0.5 stops of correct exposure. + * + * - Mode long-press changes the main digits to show raw sensor lux measurements. + */ + #include "movement.h" #include "opt3001.h" diff --git a/movement/watch_faces/sensor/thermistor_logging_face.h b/movement/watch_faces/sensor/thermistor_logging_face.h index 4ba593ec..0debfbf4 100644 --- a/movement/watch_faces/sensor/thermistor_logging_face.h +++ b/movement/watch_faces/sensor/thermistor_logging_face.h @@ -25,6 +25,34 @@ #ifndef THERMISTOR_LOGGING_FACE_H_ #define THERMISTOR_LOGGING_FACE_H_ +/* + * THERMISTOR LOGGING (aka Temperature Log) + * + * This watch face automatically logs the temperature once an hour, and + * maintains a 36-hour log of readings. This watch face is admittedly rather + * complex, and bears some explanation. + * + * The main display shows the letters “TL” in the top left, indicating the + * name of the watch face. At the top right, it displays the index of the + * reading; 0 represents the most recent reading taken, 1 represents one + * hour earlier, etc. The bottom line in this mode displays the logged + * temperature. + * + * A short press of the “Alarm” button advances to the next oldest reading; + * you will see the number at the top right advance from 0 to 1 to 2, all + * the way to 35, the oldest reading available. + * + * A short press of the “Light” button will briefly display the timestamp + * of the reading. The letters at the top left will display the word “At”, + * and the main line will display the timestamp of the currently displayed + * data point. The number in the top right will display the day of the month + * for the given data point; for example, you can read “At 22 3:00 PM” as + * ”At 3:00 PM on the 22nd”. + * + * If you need to illuminate the LED to read the data point, long press the + * Light button and release it. + */ + #include "movement.h" #include "watch.h" diff --git a/movement/watch_faces/sensor/thermistor_readout_face.h b/movement/watch_faces/sensor/thermistor_readout_face.h index 7361164e..10cdcc19 100644 --- a/movement/watch_faces/sensor/thermistor_readout_face.h +++ b/movement/watch_faces/sensor/thermistor_readout_face.h @@ -25,6 +25,29 @@ #ifndef THERMISTOR_READOUT_FACE_H_ #define THERMISTOR_READOUT_FACE_H_ +/* + * THERMISTOR READOUT (aka Temperature Display) + * + * This watch face is designed to work with either the Temperature + GPIO + * sensor board or the Temperature + Light sensor board. It reads the current + * temperature from the thermistor voltage divider on the sensor board, and + * displays the current temperature in degrees Celsius. + * + * When the watch is on your wrist, your body heat interferes with an ambient + * temperature reading, but if you set it on a bedside table, strap it to your + * bike handlebars or place it outside of your tent while camping, this watch + * face can act as a digital thermometer for displaying ambient conditions. + * + * The temperature sensor watch face automatically samples the temperature + * once every five seconds, and it illuminates the Signal indicator just + * before taking a reading. + * + * Pressing the ALARM button toggles the unit display from Celsius to + * Fahrenheit. Technically this sets the global “Metric / Imperial” flag, so + * any other watch face that displays localizable units will display them in + * the system selected here. + */ + #include "movement.h" void thermistor_readout_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/movement/watch_faces/sensor/thermistor_testing_face.c b/movement/watch_faces/sensor/thermistor_testing_face.c index 2910fbd9..10addc04 100644 --- a/movement/watch_faces/sensor/thermistor_testing_face.c +++ b/movement/watch_faces/sensor/thermistor_testing_face.c @@ -28,11 +28,6 @@ #include "thermistor_driver.h" #include "watch.h" -// This watch face is designed for testing temperature sensor boards. -// It displays temperature readings at a relatively fast rate of 8 Hz, -// and disables low energy mode so my testing device doesn't sleep. -// You more than likely want to use thermistor_readout_face instead. - static void _thermistor_testing_face_update_display(bool in_fahrenheit) { thermistor_driver_enable(); float temperature_c = thermistor_driver_get_temperature(); diff --git a/movement/watch_faces/sensor/thermistor_testing_face.h b/movement/watch_faces/sensor/thermistor_testing_face.h index 656f58d5..9721484a 100644 --- a/movement/watch_faces/sensor/thermistor_testing_face.h +++ b/movement/watch_faces/sensor/thermistor_testing_face.h @@ -25,6 +25,17 @@ #ifndef THERMISTOR_TESTING_FACE_H_ #define THERMISTOR_TESTING_FACE_H_ +/* + * THERMISTOR TESTING FACE + * + * This watch face is designed for testing temperature sensor boards. + * It displays temperature readings at a relatively fast rate of 8 Hz, + * and disables low energy mode so my testing device doesn't sleep. + * You more than likely want to use thermistor_readout_face instead. + * + * Press ALARM to toggle display of metric vs. imperial units. + */ + #include "movement.h" void thermistor_testing_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/movement/watch_faces/settings/finetune_face.c b/movement/watch_faces/settings/finetune_face.c index 3b326b93..67680ed9 100644 --- a/movement/watch_faces/settings/finetune_face.c +++ b/movement/watch_faces/settings/finetune_face.c @@ -20,21 +20,6 @@ * 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. - * - * FineTune face allows to align watch with sub-second precision in 25/250ms accuracy. - * Counts time since previous finetune, and allows to calculate & apply ppm correction for nanosec. - * - * Main screen - adjust delay (light/alarm) - * Long mode press - show hours since previous finetune - * Long mode press - show calculated ppm correction. You can apply it with long light, or just reset finetune timer with long alarm. - * - * Finetune will apply crystal aging correction on every finetune save (as aging is calculated since "last finetune" timestamp) - but you should worry - * about aging only on second/third years of watch calibration (if you are really looking at less than 10 seconds per year of error). - * - * Warning, do not use at the first second of a month, as you might stay at the same month and it will surprise you. - * Just wait 1 second...We are not fully replicating RTC timer behavior when RTC is off. - * Simulating months and years is... too much complexity. - * */ #include <stdlib.h> diff --git a/movement/watch_faces/settings/finetune_face.h b/movement/watch_faces/settings/finetune_face.h index 6a80bf2b..95ac5705 100644 --- a/movement/watch_faces/settings/finetune_face.h +++ b/movement/watch_faces/settings/finetune_face.h @@ -25,6 +25,34 @@ #ifndef FINETUNE_FACE_H_ #define FINETUNE_FACE_H_ +/* + * FINETUNE face + * + * FineTune face allows to align watch with sub-second precision in 25/250ms + * accuracy. Counts time since previous finetune, and allows to calculate & + * apply ppm correction for nanosec. + * + * Best used in conjunction with the NANOSEC face. + * + * Main screen - adjust delay (light/alarm) + * Long MODE press - show hours since previous finetune + * Long MODE press - show calculated ppm correction. + * You can apply it with long LIGHT, or just reset finetune timer with long ALARM. + * + * Finetune will apply crystal aging correction on every finetune save + * (as aging is calculated since "last finetune" timestamp); but you should + * worry about aging only on second/third years of watch calibration (if you + * are really looking at less than 10 seconds per year of error). + * + * Warning, do not use at the first second of a month, as you might stay at + * the same month and it will surprise you. Just wait 1 second...We are not + * fully replicating RTC timer behavior when RTC is off. + * Simulating months and years is... too much complexity. + * + * For full usage instructions, please refer to the wiki: + * https://www.sensorwatch.net/docs/watchfaces/nanosec/ + */ + #include "movement.h" typedef struct { diff --git a/movement/watch_faces/settings/nanosec_face.c b/movement/watch_faces/settings/nanosec_face.c index b9655c1d..72fdb729 100644 --- a/movement/watch_faces/settings/nanosec_face.c +++ b/movement/watch_faces/settings/nanosec_face.c @@ -22,33 +22,6 @@ * SOFTWARE. */ -/* - * The goal of nanosec face is dramatic improvement of SensorWatch accuracy. - * Minimum goal is <60 seconds of error per year. Full success is if we can reach <15 seconds per year (<0.47ppm error). - * - * It implements temperature correction using tempco from datasheet (and allows to adjust these) - * and allows to introduce offset fix. Therefore requires temperature sensor board. - * - * Most users will need to apply profile 3 ("default") or 2("conservative datasheet"), and tune first parameter - - * static offset (as it's different for every crystal sample). - * - * Frequency correction is dithered over 31 correction intervals (31x10 minutes or ~5 hours), to allow <0.1ppm correction resolution. - * 1ppm is 0.0864 sec per day. - * 0.1ppm is 0.00864 sec per day. - * - * To stay under 1ppm error you would need calibration of your specific instance of quartz crystal after some "burn-in" (ideally 1 year). - * - * Should improve TOTP experience. - * - * Default funing fork tempco: -0.034 ppm/°C², centered around 25°C - * We add optional cubic coefficient, which was measured in practice on my sample. - * - * Cadence (CD) - how many minutes between corrections. Default 10 minutes. - * Every minute might be too much. Every hour - slightly less power consumption but also less precision. - * - * Can compensate crystal aging (ppm/year) - but you really should be worrying about it on second/third years of watch calibration. * - */ - #include <stdlib.h> #include <string.h> #include <math.h> diff --git a/movement/watch_faces/settings/nanosec_face.h b/movement/watch_faces/settings/nanosec_face.h index 044275af..545eed29 100644 --- a/movement/watch_faces/settings/nanosec_face.h +++ b/movement/watch_faces/settings/nanosec_face.h @@ -25,6 +25,47 @@ #ifndef NANOSEC_FACE_H_ #define NANOSEC_FACE_H_ +/* + * NANOSEC face + * + * The goal of nanosec face is dramatic improvement of SensorWatch accuracy. + * Minimum goal is <60 seconds of error per year. Full success is if we can + * reach <15 seconds per year (<0.47ppm error). + * + * Best used in conjunction with the FINETUNE face. + * + * It implements temperature correction using tempco from datasheet (and + * allows to adjust these) and allows to introduce offset fix. Therefore + * requires temperature sensor board. + * + * Most users will need to apply profile 3 ("default") or 2 ("conservative + * datasheet"), and tune first parameter "static offset" (as it's different + * for every crystal sample). + * + * Frequency correction is dithered over 31 correction intervals (31x10 + * minutes or ~5 hours), to allow <0.1ppm correction resolution. + * * 1ppm is 0.0864 sec per day. + * * 0.1ppm is 0.00864 sec per day. + * + * To stay under 1ppm error you would need calibration of your specific + * instance of quartz crystal after some "burn-in" (ideally 1 year). + * + * Should improve TOTP experience. + * + * Default funing fork tempco: -0.034 ppm/°C², centered around 25°C + * We add optional cubic coefficient, which was measured in practice on my sample. + * + * Cadence (CD) - how many minutes between corrections. Default 10 minutes. + * Every minute might be too much. Every hour - slightly less power + * consumption but also less precision. + * + * Can compensate crystal aging (ppm/year) - but you really should be worrying + * about it on second/third years of watch calibration. + * + * For full usage instructions, please refer to the wiki: + * https://www.sensorwatch.net/docs/watchfaces/nanosec/ + */ + #include "movement.h" #define nanosec_profile_count 5 diff --git a/movement/watch_faces/settings/preferences_face.h b/movement/watch_faces/settings/preferences_face.h index b178bfd2..c8f36d75 100644 --- a/movement/watch_faces/settings/preferences_face.h +++ b/movement/watch_faces/settings/preferences_face.h @@ -25,6 +25,57 @@ #ifndef PREFERENCES_FACE_H_ #define PREFERENCES_FACE_H_ +/* + * PREFERENCES face + * + * The Preferences watch face allows you to configure various options on your + * Sensor Watch. Like all other screens, you advance the field you’re setting + * with the Light button, and advance its value with the Alarm button. The + * Preferences watch face labels each setting with a two-letter code on the + * top row; the following list describes each setting and their options: + * + * CL - Clock mode. + * This setting allows you to select a 12-or 24-hour clock display. All + * watch faces that support displaying the time will respect this setting; + * for example, both Simple Clock, World Clock and Sunrise/Sunset will + * display the time in 24 hour format if the 24 hour clock is selected here. + * + * BT - Button tone. + * This setting is only relevant if you installed the buzzer connector, + * and it toggles the beep when changing modes. If Y, the buzzer will + * sound a tone when Mode is pressed. Change to N to make the Mode + * button silent. + * + * TO - Timeout. + * Sets the time until screens that time out (like Settings and Time Set) + * snap back to the first screen. 60 seconds is a good default for the + * stock firmware, but if you choose a custom firmware with faces that + * you’d like to keep on screen for longer, you can set that here. + * + * LE - Low Energy mode. + * Sets the time until the watch enters its low energy sleep mode. + * Options range from 1 hour to 7 days, or Never. The more often Sensor + * Watch goes to sleep, the longer its battery will last — but you will + * lose the seconds indicator while it is asleep. This setting allows + * you to make a tradeoff between the device’s responsiveness and its + * longevity. + * + * LT - Light. + * This setting has three screens. + * The first lets you choose how long the LED should stay lit when the + * LIGHT button is pressed. Options are 1 second, 3 seconds and 5 + * seconds, or “No LED” to disable the LED entirely. + * The second screen, titled “blu” or “grn”, sets the intensity of the + * blue or green LED depending on the target Sensor Board hardware. + * Values range from 0 (off) to 15 (full intensity). + * The third screen, “red”, sets the intensity of the red LED, again + * from 0 to 15. + * On the last two screens, the LED remains on so that you can see the + * effect of mixing the two LED colors. On the Special Edition boards, + * you’ll have red, blue and a variety of shades of pink and purple to + * experiment with! + */ + #include "movement.h" void preferences_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/movement/watch_faces/settings/set_time_face.h b/movement/watch_faces/settings/set_time_face.h index c86b6376..f66dc9e4 100644 --- a/movement/watch_faces/settings/set_time_face.h +++ b/movement/watch_faces/settings/set_time_face.h @@ -25,6 +25,23 @@ #ifndef SET_TIME_FACE_H_ #define SET_TIME_FACE_H_ +/* + * SET TIME face + * + * The default method for adjusting Sensor Watch time. + * + * The Time Set watch face allows you to set the time on Sensor Watch. Use + * the LIGHT button to advance through the field you are setting, and the + * ALARM button to change the value in that field. The fields are, in order: + * Hour, Minute, Second, Year, Month, Day and Time Zone. + * + * For features like World Clock and Sunrise/Sunset to work correctly, you + * must set the time to your local time, and the time zone to your local time + * zone. This allows Sensor Watch to correctly offset the time. This also + * means that when daylight savings time starts or ends, you must update + * both the time and the time zone on this screen. + */ + #include "movement.h" void set_time_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/movement/watch_faces/settings/set_time_hackwatch_face.c b/movement/watch_faces/settings/set_time_hackwatch_face.c index c65e2ffc..fbe8cbb1 100644 --- a/movement/watch_faces/settings/set_time_hackwatch_face.c +++ b/movement/watch_faces/settings/set_time_hackwatch_face.c @@ -21,21 +21,6 @@ * 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. - * - * - * - * This is an extended version of set_time face which allow setting seconds precisely. - * To achieve that - press and hold alarm button few seconds before 00 and release exaclty as reference clock turns 00. - * All settings can go up, or down (long alarm press). - * - * The challenge is that SensorWatch display is delayed 0.5 seconds vs hardware RTC clock. It is caused by interrupts being generated by raising - * edge of counter. It means there is no way to precisely trigger at 0.5s, as events at different frequencies slightly mismatch. - * This watch face achieves this approximately by triggering at 15th out of 32Hz events. - * - * If you are <30 seconds when setting seconds - you will stay in the same minute. Otherwise - you will go to next minute. - * - * Note that changing anything will slightly delay subseconds counter. This is why this face sets seconds last - * to achiveve best precision. Still, best possible precision is achieved with finetune face. */ #include <stdlib.h> diff --git a/movement/watch_faces/settings/set_time_hackwatch_face.h b/movement/watch_faces/settings/set_time_hackwatch_face.h index 6d820660..41b91ac4 100644 --- a/movement/watch_faces/settings/set_time_hackwatch_face.h +++ b/movement/watch_faces/settings/set_time_hackwatch_face.h @@ -25,6 +25,29 @@ #ifndef SET_TIME_HACKWATCH_FACE_H_ #define SET_TIME_HACKWATCH_FACE_H_ +/* + * SET TIME HACKWATCH + * + * This is an extended version of set_time face which allow setting seconds + * precisely. To achieve that - press and hold alarm button few seconds before + * 00 and release exaclty as reference clock turns 00. + * + * All settings can go up, or down (long alarm press). + * + * The challenge is that SensorWatch display is delayed 0.5 seconds vs hardware + * RTC clock. It is caused by interrupts being generated by raising edge of + * counter. It means there is no way to precisely trigger at 0.5s, as events + * at different frequencies slightly mismatch. This watch face achieves this + * approximately by triggering at 15th out of 32Hz events. + * + * If you are <30 seconds when setting seconds - you will stay in the same + * minute. Otherwise - you will go to next minute. + * + * Note that changing anything will slightly delay subseconds counter. This + * is why this face sets seconds last to achiveve best precision. Still, + * best possible precision is achieved with finetune face. + */ + #include "movement.h" void set_time_hackwatch_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/utils/movement_bulk_installer/standard-red.uf2 b/utils/movement_bulk_installer/standard-red.uf2 Binary files differindex 385c611a..b74e539c 100644 --- a/utils/movement_bulk_installer/standard-red.uf2 +++ b/utils/movement_bulk_installer/standard-red.uf2 diff --git a/watch-library/shared/watch/watch_private_display.c b/watch-library/shared/watch/watch_private_display.c index 245b20ed..c12957d9 100644 --- a/watch-library/shared/watch/watch_private_display.c +++ b/watch-library/shared/watch/watch_private_display.c @@ -93,7 +93,7 @@ void watch_display_character(uint8_t character, uint8_t position) { } if (character == 'T' && position == 1) watch_set_pixel(1, 12); // add descender - else if (position == 0 && (character == 'B' || character == 'D')) watch_set_pixel(0, 15); // add funky ninth segment + else if (position == 0 && (character == 'B' || character == 'D' || character == '@')) watch_set_pixel(0, 15); // add funky ninth segment else if (position == 1 && (character == 'B' || character == 'D' || character == '@')) watch_set_pixel(0, 12); // add funky ninth segment } diff --git a/watch-library/shared/watch/watch_utility.c b/watch-library/shared/watch/watch_utility.c index 9e524762..64b3bb79 100644 --- a/watch-library/shared/watch/watch_utility.c +++ b/watch-library/shared/watch/watch_utility.c @@ -102,13 +102,81 @@ uint16_t watch_utility_days_since_new_year(uint16_t year, uint8_t month, uint8_t return (is_leap(year) && (month > 2) ? 1 : 0) + DAYS_SO_FAR[month - 1] + day; } +// Function taken from `src/time/__year_to_secs.c` of musl libc +// https://musl.libc.org +static uint32_t __year_to_secs(uint32_t year, int *is_leap) +{ + if (year-2ULL <= 136) { + int y = year; + int leaps = (y-68)>>2; + if (!((y-68)&3)) { + leaps--; + if (is_leap) *is_leap = 1; + } else if (is_leap) *is_leap = 0; + return 31536000*(y-70) + 86400*leaps; + } + + int cycles, centuries, leaps, rem; + + if (!is_leap) is_leap = &(int){0}; + cycles = (year-100) / 400; + rem = (year-100) % 400; + if (rem < 0) { + cycles--; + rem += 400; + } + if (!rem) { + *is_leap = 1; + centuries = 0; + leaps = 0; + } else { + if (rem >= 200) { + if (rem >= 300) centuries = 3, rem -= 300; + else centuries = 2, rem -= 200; + } else { + if (rem >= 100) centuries = 1, rem -= 100; + else centuries = 0; + } + if (!rem) { + *is_leap = 0; + leaps = 0; + } else { + leaps = rem / 4U; + rem %= 4U; + *is_leap = !rem; + } + } + + leaps += 97*cycles + 24*centuries - *is_leap; + + return (year-100) * 31536000LL + leaps * 86400LL + 946684800 + 86400; +} + +// Function taken from `src/time/__month_to_secs.c` of musl libc +// https://musl.libc.org +static int __month_to_secs(int month, int is_leap) +{ + static const int secs_through_month[] = { + 0, 31*86400, 59*86400, 90*86400, + 120*86400, 151*86400, 181*86400, 212*86400, + 243*86400, 273*86400, 304*86400, 334*86400 }; + int t = secs_through_month[month]; + if (is_leap && month >= 2) t+=86400; + return t; +} + +// Function adapted from `src/time/__tm_to_secs.c` of musl libc +// https://musl.libc.org uint32_t watch_utility_convert_to_unix_time(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, uint32_t utc_offset) { - uint32_t year_adj = year + 4800; - uint32_t leap_days = 1 + (year_adj / 4) - (year_adj / 100) + (year_adj / 400); - uint32_t days = 365 * year_adj + leap_days + watch_utility_days_since_new_year(year, month, day) - 1; - days -= 2472692; /* Adjust to Unix epoch. */ + int is_leap; + + // POSIX tm struct starts year at 1900 and month at 0 + // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/time.h.html + uint32_t timestamp = __year_to_secs(year - 1900, &is_leap); + timestamp += __month_to_secs(month - 1, is_leap); - uint32_t timestamp = days * 86400; + // Regular conversion from musl libc + timestamp += (day - 1) * 86400; timestamp += hour * 3600; timestamp += minute * 60; timestamp += second; diff --git a/watch-library/simulator/shell.html b/watch-library/simulator/shell.html index 335b9534..29fbed03 100644 --- a/watch-library/simulator/shell.html +++ b/watch-library/simulator/shell.html @@ -37,12 +37,13 @@ .highlight { fill: #fff !important; } #skinselect label { display: inline-block; - padding: 8px; + padding: 4px; background-color: black; color: white; border-radius: 8px; border: 2px solid #0e57a9; outline: 4px solid black; + margin: 4px; cursor: pointer; } #skinselect #a158wea-label { @@ -50,13 +51,16 @@ color: black; border-color: black; outline-color: #b68855ff; - + } + h2 { + margin: 8px 0; + font-size: 1em; } </style> </head> <body> -<div style="max-width: 800px; margin: 0 auto; display: flex; flex-direction: column; align-items: center;"> +<div style="max-width: 800px; min-width: 400px; margin: 0 auto; padding: 0 1em; display: flex; flex-direction: column; align-items: center;"> <h1 style="text-align: center;">Sensor Watch Emulator</h1> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1271 1311" width="320"> <defs> @@ -882,18 +886,40 @@ </g> </g> </svg> - <table cellpadding="5"><tr><td id="skinselect"> - <input type="radio" id="f91w" name="skin" value="f91w" onclick="toggleSkin()" checked><label for="f91w">F-91W</label> - <input type="radio" name="skin" id="a158wea" value="a158wea" onclick="toggleSkin()"><label id="a158wea-label" for="a158wea">A158WEA-9</label> - </td><td><a href="https://github.com/alexisphilip/Casio-F-91W">Original F-91W SVG</a> is © 2020 Alexis Philip,<br>used here under the terms of the MIT license.</td></tr></table> -</div> -<button onclick="getLocation()">Set location register (will prompt for access)</button> -<br> -<input id="input" style="width: 500px"></input> -<button id="submit" onclick="sendText()">Send</button> -<br> -<textarea id="output" rows="8" style="width: 100%"></textarea> + <div style="display: grid; grid-template-columns: 80px 1fr; align-items: center; margin: 8px 0"> + <h2>Skin</h2> + <div id="skinselect"> + <input type="radio" name="skin" id="f91w" value="f91w" onclick="setSkin(this.value)" checked><label + for="f91w">F-91W</label> + <input type="radio" name="skin" id="a158wea9" value="a158wea9" onclick="setSkin(this.value)"><label + id="a158wea-label" for="a158wea9">A158WEA-9</label> + </div> + + <h2>Volume</h2> + <div> + <input id="volume" name="volume" type="range" min="0" max="100" step="1" oninput="setVolume(this.value)" /> + </div> + + <h2>Location</h2> + <div> + <button onclick="getLocation()">Set register (will prompt for access)</button> + </div> + </div> + + <form onSubmit="sendText(); return false" style="display: flex; flex-direction: column; width: 100%"> + <textarea id="output" rows="8" style="width: 100%"></textarea> + <div style="display: flex"> + <input id="input" placeholder="Filesystem command (see filesystem.c)" style="flex-grow: 1"></input> + <button type="submit">Send</button> + </div> + </form> + + <p> + <a href="https://github.com/alexisphilip/Casio-F-91W">Original F-91W SVG</a> is © 2020 Alexis Philip, used here + under the terms of the MIT license. + </p> +</div> <script type='text/javascript'> var outputElement = document.getElementById('output'); @@ -967,20 +993,52 @@ } } - function toggleSkin() { - var isBlack = document.getElementById('f91w').checked; - Array.from(document.getElementsByClassName("f91w")).forEach( - function(element, index, array) { - element.setAttribute('style', 'display:' + (isBlack ? 'inline':'none') + ';'); + const validSkins = ["f91w", "a158wea9"]; + function setSkin(chosenSkin) { + setLocalPref("skin", chosenSkin); + validSkins.forEach(function(skin) { + Array.from(document.getElementsByClassName(skin)).forEach( + function(element) { + element.setAttribute('style', 'display:' + (skin == chosenSkin ? 'inline':'none') + ';'); } - ); - Array.from(document.getElementsByClassName("a158wea9")).forEach( - function(element, index, array) { - element.setAttribute('style', 'display:' + (isBlack ? 'none':'inline') + ';'); - } - ); + ); + }); + } + + // emulator runs on localhost:8000 which could very well be used by other + // things, so we'll scope our localStorage keys with a prefix + const localStoragePrefix = "sensorwatch_"; + function setLocalPref(key, val) { + localStorage.setItem(localStoragePrefix+key, val); + } + function getLocalPref(key, dfault) { + let pref = localStorage.getItem(localStoragePrefix+key); + if (pref === null) return dfault; + return pref; + } + + volumeGain = 0.1; + function setVolume(vol) { + setLocalPref("volume", vol); + volumeGain = Math.pow(100, (vol / 100) - 1) - 0.01; + } + + function loadPrefs() { + let vol = +getLocalPref("volume", "50"); + if (isNaN(vol) || vol < 0 || vol > 100) { + vol = 50; + } + document.getElementById("volume").value = vol; + setVolume(vol); + + let skin = getLocalPref("skin", "f91w"); + if (!validSkins.includes(skin)) { + skin = "f91w"; + } + document.getElementById(skin).checked = true; + setSkin(skin); } - toggleSkin(); + loadPrefs(); </script> {{{ SCRIPT }}} diff --git a/watch-library/simulator/watch/watch_buzzer.c b/watch-library/simulator/watch/watch_buzzer.c index 68d9a139..211235df 100644 --- a/watch-library/simulator/watch/watch_buzzer.c +++ b/watch-library/simulator/watch/watch_buzzer.c @@ -152,7 +152,7 @@ void watch_set_buzzer_on(void) { } audioContext._oscillator.frequency.value = 1e6/$0; - audioContext._gain.gain.value = 1; + audioContext._gain.gain.value = volumeGain; }, buzzer_period); } diff --git a/watch-library/simulator/watch/watch_extint.c b/watch-library/simulator/watch/watch_extint.c index cbba4c3d..b5894b95 100644 --- a/watch-library/simulator/watch/watch_extint.c +++ b/watch-library/simulator/watch/watch_extint.c @@ -22,13 +22,15 @@ * SOFTWARE. */ +#include <string.h> + #include "watch_extint.h" #include "watch_main_loop.h" #include <emscripten.h> #include <emscripten/html5.h> -static bool output_focused = false; +static bool debug_console_focused = false; static bool external_interrupt_enabled = false; static bool button_callbacks_installed = false; static ext_irq_cb_t external_interrupt_mode_callback = NULL; @@ -45,27 +47,47 @@ static const uint8_t BTN_IDS[] = { BTN_ID_ALARM, BTN_ID_LIGHT, BTN_ID_MODE }; static EM_BOOL watch_invoke_interrupt_callback(const uint8_t button_id, watch_interrupt_trigger trigger); static EM_BOOL watch_invoke_key_callback(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData) { - if (output_focused || keyEvent->repeat) return EM_FALSE; - - const char *key = keyEvent->key; - if (key[1] != 0) return EM_FALSE; + if (debug_console_focused || keyEvent->repeat) return EM_FALSE; uint8_t button_id; - switch (key[0]) { - case 'A': - case 'a': - button_id = BTN_ID_ALARM; - break; - case 'L': - case 'l': - button_id = BTN_ID_LIGHT; - break; - case 'M': - case 'm': - button_id = BTN_ID_MODE; - break; - default: - return EM_FALSE; + const char *key = keyEvent->key; + if (key[1] == 0) { + // event is from a plain letter key + switch (key[0]) { + case 'A': + case 'a': + button_id = BTN_ID_ALARM; + break; + case 'L': + case 'l': + button_id = BTN_ID_LIGHT; + break; + case 'M': + case 'm': + button_id = BTN_ID_MODE; + break; + default: + return EM_FALSE; + } + } else if (strncmp(key, "Arrow", 5) == 0) { + // event is from one of the arrow keys + switch(key[5]) { + case 'U': // ArrowUp + button_id = BTN_ID_LIGHT; + break; + case 'D': // ArrowDown + case 'L': // ArrowLeft + button_id = BTN_ID_MODE; + break; + case 'R': // ArrowRight + button_id = BTN_ID_ALARM; + break; + default: + return EM_FALSE; + } + } else { + // another kind of key + return EM_FALSE; } watch_interrupt_trigger trigger = eventType == EMSCRIPTEN_EVENT_KEYDOWN ? INTERRUPT_TRIGGER_RISING : INTERRUPT_TRIGGER_FALLING; @@ -86,7 +108,7 @@ static EM_BOOL watch_invoke_touch_callback(int eventType, const EmscriptenTouchE } static EM_BOOL watch_invoke_focus_callback(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData) { - output_focused = eventType == EMSCRIPTEN_EVENT_FOCUS; + debug_console_focused = eventType == EMSCRIPTEN_EVENT_FOCUS; return EM_TRUE; } @@ -98,6 +120,10 @@ static void watch_install_button_callbacks(void) { emscripten_set_focus_callback(target_output, NULL, EM_FALSE, watch_invoke_focus_callback); emscripten_set_blur_callback(target_output, NULL, EM_FALSE, watch_invoke_focus_callback); + const char *target_input = "#input"; + emscripten_set_focus_callback(target_input, NULL, EM_FALSE, watch_invoke_focus_callback); + emscripten_set_blur_callback(target_input, NULL, EM_FALSE, watch_invoke_focus_callback); + for (int i = 0, count = sizeof(BTN_IDS) / sizeof(BTN_IDS[0]); i < count; i++) { char target[] = "#btn_"; target[4] = BTN_IDS[i] + '0'; diff --git a/watch-library/simulator/watch/watch_rtc.c b/watch-library/simulator/watch/watch_rtc.c index fa80d6b4..f6279eed 100644 --- a/watch-library/simulator/watch/watch_rtc.c +++ b/watch-library/simulator/watch/watch_rtc.c @@ -97,8 +97,7 @@ void watch_rtc_register_periodic_callback(ext_irq_cb_t callback, uint8_t frequen // 0x01 (1 Hz) will have 7 leading zeros for PER7. 0xF0 (128 Hz) will have no leading zeroes for PER0. uint8_t per_n = __builtin_clz(tmp); - // this also maps nicely to an index for our list of tick callbacks. - double interval = 1000 / frequency; // in msec + double interval = 1000.0 / frequency; // in msec if (tick_callbacks[per_n] != -1) emscripten_clear_interval(tick_callbacks[per_n]); tick_callbacks[per_n] = emscripten_set_interval(watch_invoke_periodic_callback, interval, (void *)callback); @@ -115,7 +114,7 @@ void watch_rtc_disable_periodic_callback(uint8_t frequency) { void watch_rtc_disable_matching_periodic_callbacks(uint8_t mask) { for (int i = 0; i < 8; i++) { - if (tick_callbacks[i] != -1 && (mask & (1 << (7 - i))) != 0) { + if (tick_callbacks[i] != -1 && (mask & (1 << i)) != 0) { emscripten_clear_interval(tick_callbacks[i]); tick_callbacks[i] = -1; } |