summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml3
-rw-r--r--make.mk5
-rw-r--r--movement/make/Makefile8
-rwxr-xr-xmovement/make/make_alternate_fw.sh4
-rw-r--r--movement/movement.c108
-rw-r--r--movement/movement.h5
-rw-r--r--movement/movement_config.h2
-rw-r--r--movement/movement_faces.h8
-rw-r--r--movement/template/template.c1
-rw-r--r--movement/watch_faces/clock/beats_face.c24
-rw-r--r--movement/watch_faces/clock/beats_face.h35
-rw-r--r--movement/watch_faces/clock/day_night_percentage_face.c139
-rw-r--r--movement/watch_faces/clock/day_night_percentage_face.h66
-rw-r--r--movement/watch_faces/clock/decimal_time_face.h7
-rw-r--r--movement/watch_faces/clock/mars_time_face.h26
-rw-r--r--movement/watch_faces/clock/minute_repeater_decimal_face.c238
-rw-r--r--movement/watch_faces/clock/minute_repeater_decimal_face.h84
-rw-r--r--movement/watch_faces/clock/repetition_minute_face.c2
-rw-r--r--movement/watch_faces/clock/repetition_minute_face.h8
-rw-r--r--movement/watch_faces/clock/simple_clock_bin_led_face.h6
-rw-r--r--movement/watch_faces/clock/simple_clock_face.c4
-rw-r--r--movement/watch_faces/clock/simple_clock_face.h9
-rw-r--r--movement/watch_faces/clock/weeknumber_clock_face.h8
-rw-r--r--movement/watch_faces/clock/world_clock2_face.c73
-rw-r--r--movement/watch_faces/clock/world_clock2_face.h59
-rw-r--r--movement/watch_faces/clock/world_clock_face.h22
-rw-r--r--movement/watch_faces/clock/wyoscan_face.c2
-rw-r--r--movement/watch_faces/clock/wyoscan_face.h25
-rw-r--r--movement/watch_faces/complication/activity_face.h7
-rw-r--r--movement/watch_faces/complication/alarm_face.c27
-rw-r--r--movement/watch_faces/complication/alarm_face.h31
-rw-r--r--movement/watch_faces/complication/astronomy_face.h41
-rw-r--r--movement/watch_faces/complication/blinky_face.h26
-rw-r--r--movement/watch_faces/complication/breathing_face.h11
-rw-r--r--movement/watch_faces/complication/couch_to_5k_face.c267
-rw-r--r--movement/watch_faces/complication/couch_to_5k_face.h87
-rw-r--r--movement/watch_faces/complication/countdown_face.c15
-rw-r--r--movement/watch_faces/complication/countdown_face.h27
-rw-r--r--movement/watch_faces/complication/counter_face.h12
-rw-r--r--movement/watch_faces/complication/databank_face.c8
-rw-r--r--movement/watch_faces/complication/databank_face.h17
-rw-r--r--movement/watch_faces/complication/day_one_face.c221
-rw-r--r--movement/watch_faces/complication/day_one_face.h36
-rw-r--r--movement/watch_faces/complication/discgolf_face.c2
-rw-r--r--movement/watch_faces/complication/discgolf_face.h25
-rw-r--r--movement/watch_faces/complication/dual_timer_face.h17
-rw-r--r--movement/watch_faces/complication/flashlight_face.h6
-rw-r--r--movement/watch_faces/complication/geomancy_face.h6
-rw-r--r--movement/watch_faces/complication/habit_face.h4
-rw-r--r--movement/watch_faces/complication/interval_face.c53
-rw-r--r--movement/watch_faces/complication/interval_face.h58
-rw-r--r--movement/watch_faces/complication/invaders_face.h4
-rw-r--r--movement/watch_faces/complication/kitchen_conversions_face.c480
-rw-r--r--movement/watch_faces/complication/kitchen_conversions_face.h87
-rw-r--r--movement/watch_faces/complication/moon_phase_face.h24
-rw-r--r--movement/watch_faces/complication/morsecalc_face.c83
-rw-r--r--movement/watch_faces/complication/morsecalc_face.h92
-rw-r--r--movement/watch_faces/complication/orrery_face.c1
-rw-r--r--movement/watch_faces/complication/orrery_face.h42
-rw-r--r--movement/watch_faces/complication/planetary_hours_face.h12
-rw-r--r--movement/watch_faces/complication/planetary_time_face.h10
-rw-r--r--movement/watch_faces/complication/probability_face.h12
-rw-r--r--movement/watch_faces/complication/pulsometer_face.h27
-rw-r--r--movement/watch_faces/complication/randonaut_face.h8
-rw-r--r--movement/watch_faces/complication/ratemeter_face.h10
-rw-r--r--movement/watch_faces/complication/rpn_calculator_alt_face.c33
-rw-r--r--movement/watch_faces/complication/rpn_calculator_alt_face.h34
-rw-r--r--movement/watch_faces/complication/rpn_calculator_face.h9
-rw-r--r--movement/watch_faces/complication/sailing_face.c33
-rw-r--r--movement/watch_faces/complication/sailing_face.h38
-rw-r--r--movement/watch_faces/complication/ships_bell_face.h5
-rw-r--r--movement/watch_faces/complication/simple_coin_flip_face.c139
-rw-r--r--movement/watch_faces/complication/simple_coin_flip_face.h62
-rw-r--r--movement/watch_faces/complication/solstice_face.c236
-rw-r--r--movement/watch_faces/complication/solstice_face.h64
-rw-r--r--movement/watch_faces/complication/stock_stopwatch_face.h32
-rw-r--r--movement/watch_faces/complication/stopwatch_face.h11
-rw-r--r--movement/watch_faces/complication/sunrise_sunset_face.h14
-rw-r--r--movement/watch_faces/complication/tachymeter_face.h43
-rw-r--r--movement/watch_faces/complication/tally_face.h14
-rw-r--r--movement/watch_faces/complication/tarot_face.h6
-rw-r--r--movement/watch_faces/complication/tempchart_face.c5
-rw-r--r--movement/watch_faces/complication/tempchart_face.h13
-rw-r--r--movement/watch_faces/complication/time_left_face.h6
-rw-r--r--movement/watch_faces/complication/timer_face.c6
-rw-r--r--movement/watch_faces/complication/timer_face.h7
-rw-r--r--movement/watch_faces/complication/tomato_face.c10
-rw-r--r--movement/watch_faces/complication/tomato_face.h21
-rw-r--r--movement/watch_faces/complication/toss_up_face.h6
-rw-r--r--movement/watch_faces/complication/totp_face.c33
-rw-r--r--movement/watch_faces/complication/totp_face.h52
-rw-r--r--movement/watch_faces/complication/totp_face_lfs.c42
-rw-r--r--movement/watch_faces/complication/totp_face_lfs.h48
-rw-r--r--movement/watch_faces/complication/tuning_tones_face.c140
-rw-r--r--movement/watch_faces/complication/tuning_tones_face.h57
-rw-r--r--movement/watch_faces/complication/wake_face.c12
-rw-r--r--movement/watch_faces/complication/wake_face.h17
-rw-r--r--movement/watch_faces/demo/character_set_face.h11
-rw-r--r--movement/watch_faces/demo/chirpy_demo_face.h5
-rw-r--r--movement/watch_faces/demo/demo_face.h11
-rw-r--r--movement/watch_faces/demo/frequency_correction_face.h12
-rw-r--r--movement/watch_faces/demo/hello_there_face.h7
-rw-r--r--movement/watch_faces/demo/lis2dw_logging_face.h8
-rw-r--r--movement/watch_faces/demo/voltage_face.h11
-rw-r--r--movement/watch_faces/sensor/accelerometer_data_acquisition_face.h6
-rw-r--r--movement/watch_faces/sensor/lightmeter_face.c31
-rw-r--r--movement/watch_faces/sensor/lightmeter_face.h31
-rw-r--r--movement/watch_faces/sensor/thermistor_logging_face.h28
-rw-r--r--movement/watch_faces/sensor/thermistor_readout_face.h23
-rw-r--r--movement/watch_faces/sensor/thermistor_testing_face.c5
-rw-r--r--movement/watch_faces/sensor/thermistor_testing_face.h11
-rw-r--r--movement/watch_faces/settings/finetune_face.c15
-rw-r--r--movement/watch_faces/settings/finetune_face.h28
-rw-r--r--movement/watch_faces/settings/nanosec_face.c28
-rw-r--r--movement/watch_faces/settings/nanosec_face.h41
-rw-r--r--movement/watch_faces/settings/preferences_face.c12
-rw-r--r--movement/watch_faces/settings/preferences_face.h51
-rw-r--r--movement/watch_faces/settings/save_load_face.c152
-rw-r--r--movement/watch_faces/settings/save_load_face.h82
-rw-r--r--movement/watch_faces/settings/set_time_face.h17
-rw-r--r--movement/watch_faces/settings/set_time_hackwatch_face.c15
-rw-r--r--movement/watch_faces/settings/set_time_hackwatch_face.h23
-rw-r--r--utils/movement_bulk_installer/standard-red.uf2bin217088 -> 217088 bytes
-rw-r--r--watch-library/hardware/watch/watch_buzzer.c1
-rw-r--r--watch-library/hardware/watch/watch_rtc.c4
-rw-r--r--watch-library/shared/watch/watch_buzzer.h2
-rw-r--r--watch-library/shared/watch/watch_private_buzzer.c13
-rw-r--r--watch-library/shared/watch/watch_private_buzzer.h33
-rw-r--r--watch-library/shared/watch/watch_private_display.c2
-rw-r--r--watch-library/shared/watch/watch_utility.c78
-rw-r--r--watch-library/simulator/shell.html110
-rw-r--r--watch-library/simulator/watch/watch_buzzer.c3
-rw-r--r--watch-library/simulator/watch/watch_extint.c68
-rw-r--r--watch-library/simulator/watch/watch_rtc.c9
134 files changed, 4320 insertions, 792 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:
diff --git a/make.mk b/make.mk
index a83108bd..955ea310 100644
--- a/make.mk
+++ b/make.mk
@@ -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..42dfc644 100644
--- a/movement/make/Makefile
+++ b/movement/make/Makefile
@@ -118,6 +118,14 @@ SRCS += \
../watch_faces/complication/flashlight_face.c \
../watch_faces/clock/decimal_time_face.c \
../watch_faces/clock/wyoscan_face.c \
+ ../watch_faces/settings/save_load_face.c \
+ ../watch_faces/clock/day_night_percentage_face.c \
+ ../watch_faces/complication/simple_coin_flip_face.c \
+ ../watch_faces/complication/solstice_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 \
+ ../watch_faces/complication/kitchen_conversions_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/make/make_alternate_fw.sh b/movement/make/make_alternate_fw.sh
index c65e72d3..df27403f 100755
--- a/movement/make/make_alternate_fw.sh
+++ b/movement/make/make_alternate_fw.sh
@@ -21,12 +21,12 @@ do
for color in "${colors[@]}"
do
COLOR=$(echo "$color" | tr '[:lower:]' '[:upper:]')
- make clean
+ make COLOR=$COLOR clean
make COLOR=$COLOR FIRMWARE=$VARIANT
mv "build/watch.uf2" "$fw_dir/$variant-$color.uf2"
done
rm -rf ./build-sim
- emmake make FIRMWARE=$VARIANT
+ emmake make COLOR=GREEN FIRMWARE=$VARIANT
mkdir "$sim_dir/$variant/"
mv "build-sim/watch.wasm" "$sim_dir/$variant/"
mv "build-sim/watch.js" "$sim_dir/$variant/"
diff --git a/movement/movement.c b/movement/movement.c
index f7c522e7..50f5f0fe 100644
--- a/movement/movement.c
+++ b/movement/movement.c
@@ -84,9 +84,9 @@
#define MOVEMENT_DEFAULT_TIMEOUT_INTERVAL 0
#endif
-// Default to switch to low energy mode after 1 hour
+// Default to switch to low energy mode after 2 hours
#ifndef MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL
-#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 1
+#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 2
#endif
// Default to 1 second led duration
@@ -101,7 +101,7 @@
movement_state_t movement_state;
void * watch_face_contexts[MOVEMENT_NUM_FACES];
watch_date_time scheduled_tasks[MOVEMENT_NUM_FACES];
-const int32_t movement_le_inactivity_deadlines[8] = {INT_MAX, 3600, 7200, 21600, 43200, 86400, 172800, 604800};
+const int32_t movement_le_inactivity_deadlines[8] = {INT_MAX, 600, 3600, 7200, 21600, 43200, 86400, 604800};
const int16_t movement_timeout_inactivity_deadlines[4] = {60, 120, 300, 1800};
movement_event_t event;
@@ -257,7 +257,7 @@ bool movement_default_loop_handler(movement_event_t event, movement_settings_t *
movement_illuminate_led();
break;
case EVENT_MODE_LONG_PRESS:
- if (MOVEMENT_SECONDARY_FACE_INDEX && movement_state.current_watch_face == 0) {
+ if (MOVEMENT_SECONDARY_FACE_INDEX && movement_state.current_face_idx == 0) {
movement_move_to_face(MOVEMENT_SECONDARY_FACE_INDEX);
} else {
movement_move_to_face(0);
@@ -272,25 +272,25 @@ bool movement_default_loop_handler(movement_event_t event, movement_settings_t *
void movement_move_to_face(uint8_t watch_face_index) {
movement_state.watch_face_changed = true;
- movement_state.next_watch_face = watch_face_index;
+ movement_state.next_face_idx = watch_face_index;
}
void movement_move_to_next_face(void) {
uint16_t face_max;
if (MOVEMENT_SECONDARY_FACE_INDEX) {
- face_max = (movement_state.current_watch_face < (int16_t)MOVEMENT_SECONDARY_FACE_INDEX) ? MOVEMENT_SECONDARY_FACE_INDEX : MOVEMENT_NUM_FACES;
+ face_max = (movement_state.current_face_idx < (int16_t)MOVEMENT_SECONDARY_FACE_INDEX) ? MOVEMENT_SECONDARY_FACE_INDEX : MOVEMENT_NUM_FACES;
} else {
face_max = MOVEMENT_NUM_FACES;
}
- movement_move_to_face((movement_state.current_watch_face + 1) % face_max);
+ movement_move_to_face((movement_state.current_face_idx + 1) % face_max);
}
void movement_schedule_background_task(watch_date_time date_time) {
- movement_schedule_background_task_for_face(movement_state.current_watch_face, date_time);
+ movement_schedule_background_task_for_face(movement_state.current_face_idx, date_time);
}
void movement_cancel_background_task(void) {
- movement_cancel_background_task_for_face(movement_state.current_watch_face);
+ movement_cancel_background_task_for_face(movement_state.current_face_idx);
}
void movement_schedule_background_task_for_face(uint8_t watch_face_index, watch_date_time date_time) {
@@ -318,25 +318,31 @@ void movement_request_wake() {
_movement_reset_inactivity_countdown();
}
-void movement_play_signal(void) {
- 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 end_buzzing() {
+ movement_state.is_buzzing = false;
}
-void movement_play_tune(void) {
- if (!watch_is_buzzer_or_led_enabled()) {
- watch_enable_buzzer();
- watch_buzzer_play_sequence(signal_tune, watch_disable_buzzer);
+void end_buzzing_and_disable_buzzer(void) {
+ end_buzzing();
+ watch_disable_buzzer();
+}
+
+void movement_play_signal(void) {
+ void *maybe_disable_buzzer = end_buzzing_and_disable_buzzer;
+ if (watch_is_buzzer_or_led_enabled()) {
+ maybe_disable_buzzer = end_buzzing;
} else {
- watch_buzzer_play_sequence(signal_tune, NULL);
+ watch_enable_buzzer();
+ }
+ movement_state.is_buzzing = true;
+ watch_buzzer_play_sequence(signal_tune, maybe_disable_buzzer);
+ if (movement_state.le_mode_ticks == -1) {
+ // the watch is asleep. wake it up for "1" round through the main loop.
+ // the sleep_mode_app_loop will notice the is_buzzing and note that it
+ // only woke up to beep and then it will spinlock until the callback
+ // turns off the is_buzzing flag.
+ movement_state.needs_wake = true;
+ movement_state.le_mode_ticks = 1;
}
}
@@ -441,7 +447,7 @@ void app_setup(void) {
watch_faces[i].setup(&movement_state.settings, i, &watch_face_contexts[i]);
}
- watch_faces[movement_state.current_watch_face].activate(&movement_state.settings, watch_face_contexts[movement_state.current_watch_face]);
+ watch_faces[movement_state.current_face_idx].activate(&movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
event.subsecond = 0;
event.event_type = EVENT_ACTIVATE;
}
@@ -461,7 +467,7 @@ static void _sleep_mode_app_loop(void) {
if (movement_state.needs_background_tasks_handled) _movement_handle_background_tasks();
event.event_type = EVENT_LOW_ENERGY_UPDATE;
- watch_faces[movement_state.current_watch_face].loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_watch_face]);
+ watch_faces[movement_state.current_face_idx].loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
// if we need to wake immediately, do it!
if (movement_state.needs_wake) return;
@@ -471,16 +477,20 @@ static void _sleep_mode_app_loop(void) {
}
bool app_loop(void) {
+ const watch_face_t *wf = &watch_faces[movement_state.current_face_idx];
+ bool woke_up_for_buzzer = false;
if (movement_state.watch_face_changed) {
if (movement_state.settings.bit.button_should_sound) {
// low note for nonzero case, high note for return to watch_face 0
- watch_buzzer_play_note(movement_state.next_watch_face ? BUZZER_NOTE_C7 : BUZZER_NOTE_C8, 50);
+ watch_buzzer_play_note(movement_state.next_face_idx ? BUZZER_NOTE_C7 : BUZZER_NOTE_C8, 50);
}
- watch_faces[movement_state.current_watch_face].resign(&movement_state.settings, watch_face_contexts[movement_state.current_watch_face]);
- movement_state.current_watch_face = movement_state.next_watch_face;
+ wf->resign(&movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
+ movement_state.current_face_idx = movement_state.next_face_idx;
+ // we have just updated the face idx, so we must recache the watch face pointer.
+ wf = &watch_faces[movement_state.current_face_idx];
watch_clear_display();
movement_request_tick_frequency(1);
- watch_faces[movement_state.current_watch_face].activate(&movement_state.settings, watch_face_contexts[movement_state.current_watch_face]);
+ wf->activate(&movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
event.subsecond = 0;
event.event_type = EVENT_ACTIVATE;
movement_state.watch_face_changed = false;
@@ -514,18 +524,24 @@ bool app_loop(void) {
// _sleep_mode_app_loop takes over at this point and loops until le_mode_ticks is reset by the extwake handler,
// or wake is requested using the movement_request_wake function.
_sleep_mode_app_loop();
- // as soon as _sleep_mode_app_loop returns, we reactivate ourselves.
+ // as soon as _sleep_mode_app_loop returns, we prepare to reactivate
+ // ourselves, but first, we check to see if we woke up for the buzzer:
+ if (movement_state.is_buzzing) {
+ woke_up_for_buzzer = true;
+ }
event.event_type = EVENT_ACTIVATE;
// this is a hack tho: waking from sleep mode, app_setup does get called, but it happens before we have reset our ticks.
// need to figure out if there's a better heuristic for determining how we woke up.
app_setup();
}
+ // default to being allowed to sleep by the face.
static bool can_sleep = true;
if (event.event_type) {
event.subsecond = movement_state.subsecond;
- can_sleep = watch_faces[movement_state.current_watch_face].loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_watch_face]);
+ // the first trip through the loop overrides the can_sleep state
+ can_sleep = wf->loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
event.event_type = EVENT_NONE;
}
@@ -537,9 +553,16 @@ bool app_loop(void) {
event.event_type = EVENT_TIMEOUT;
}
event.subsecond = movement_state.subsecond;
- watch_faces[movement_state.current_watch_face].loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_watch_face]);
+ // if we run through the loop again to time out, we need to reconsider whether or not we can sleep.
+ // if the first trip said true, but this trip said false, we need the false to override, thus
+ // we will be using boolean AND:
+ //
+ // first trip | can sleep | cannot sleep | can sleep | cannot sleep
+ // second trip | can sleep | cannot sleep | cannot sleep | can sleep
+ // && | can sleep | cannot sleep | cannot sleep | cannot sleep
+ can_sleep = can_sleep && wf->loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
event.event_type = EVENT_NONE;
- if (movement_state.settings.bit.to_always && movement_state.current_watch_face != 0) {
+ if (movement_state.settings.bit.to_always && movement_state.current_face_idx != 0) {
// ...but if the user has "timeout always" set, give it the boot.
movement_move_to_face(0);
}
@@ -596,8 +619,13 @@ bool app_loop(void) {
// if the watch face changed, we can't sleep because we need to update the display.
if (movement_state.watch_face_changed) can_sleep = false;
- // if the buzzer or the LED is on, we need to stay awake to keep the TCC running.
- if (movement_state.is_buzzing || movement_state.light_ticks != -1) can_sleep = false;
+ // if we woke up for the buzzer, stay awake until it's finished.
+ if (woke_up_for_buzzer) {
+ while(watch_is_buzzer_or_led_enabled());
+ }
+
+ // if the LED is on, we need to stay awake to keep the TCC running.
+ if (movement_state.light_ticks != -1) can_sleep = false;
return can_sleep;
}
@@ -660,13 +688,13 @@ void cb_fast_tick(void) {
// Notice: is it possible that two or more buttons have an identical timestamp? In this case
// only one of these buttons would receive the long press event. Don't bother for now...
if (movement_state.light_down_timestamp > 0)
- if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1)
+ if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1)
event.event_type = EVENT_LIGHT_LONG_PRESS;
if (movement_state.mode_down_timestamp > 0)
- if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1)
+ if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1)
event.event_type = EVENT_MODE_LONG_PRESS;
if (movement_state.alarm_down_timestamp > 0)
- if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1)
+ if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1)
event.event_type = EVENT_ALARM_LONG_PRESS;
// this is just a fail-safe; fast tick should be disabled as soon as the button is up, the LED times out, and/or the alarm finishes.
// but if for whatever reason it isn't, this forces the fast tick off after 20 seconds.
diff --git a/movement/movement.h b/movement/movement.h
index 5f30dfb8..1dabfbc5 100644
--- a/movement/movement.h
+++ b/movement/movement.h
@@ -244,8 +244,8 @@ typedef struct {
movement_settings_t settings;
// transient properties
- int16_t current_watch_face;
- int16_t next_watch_face;
+ int16_t current_face_idx;
+ int16_t next_face_idx;
bool watch_face_changed;
bool fast_tick_enabled;
int16_t fast_ticks;
@@ -307,7 +307,6 @@ 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_config.h b/movement/movement_config.h
index 3ce27ea6..10a30af7 100644
--- a/movement/movement_config.h
+++ b/movement/movement_config.h
@@ -49,7 +49,7 @@ const watch_face_t watch_faces[] = {
*/
#define MOVEMENT_SECONDARY_FACE_INDEX (MOVEMENT_NUM_FACES - 2) // or (0)
-/* Custom hourly chime tune. Check movement_custom_signal_tunes.h for options */
+/* Custom hourly chime tune. Check movement_custom_signal_tunes.h for options. */
#define SIGNAL_TUNE_DEFAULT
/* Determines the intensity of the led colors
diff --git a/movement/movement_faces.h b/movement/movement_faces.h
index ff34c063..7feb0f40 100644
--- a/movement/movement_faces.h
+++ b/movement/movement_faces.h
@@ -95,6 +95,14 @@
#include "flashlight_face.h"
#include "decimal_time_face.h"
#include "wyoscan_face.h"
+#include "save_load_face.h"
+#include "day_night_percentage_face.h"
+#include "simple_coin_flip_face.h"
+#include "solstice_face.h"
+#include "couch_to_5k_face.h"
+#include "minute_repeater_decimal_face.h"
+#include "tuning_tones_face.h"
+#include "kitchen_conversions_face.h"
// New includes go above this line.
#endif // MOVEMENT_FACES_H_
diff --git a/movement/template/template.c b/movement/template/template.c
index e03db561..fe2723b8 100644
--- a/movement/template/template.c
+++ b/movement/template/template.c
@@ -28,6 +28,7 @@
void <#watch_face_name#>_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(<#watch_face_name#>_state_t));
memset(*context_ptr, 0, sizeof(<#watch_face_name#>_state_t));
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/day_night_percentage_face.c b/movement/watch_faces/clock/day_night_percentage_face.c
new file mode 100644
index 00000000..86f07f9d
--- /dev/null
+++ b/movement/watch_faces/clock/day_night_percentage_face.c
@@ -0,0 +1,139 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Wesley Aptekar-Cassels
+ *
+ * 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>
+#include "day_night_percentage_face.h"
+#include "watch_utility.h"
+#include "sunriset.h"
+
+// fmod but handle negatives right
+static double better_fmod(double x, double y) {
+ return fmod(fmod(x, y) + y, y);
+}
+
+static void recalculate(watch_date_time utc_now, day_night_percentage_state_t *state) {
+ movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1);
+
+ if (movement_location.reg == 0) {
+ state->result = -2;
+ return;
+ }
+
+ // Weird quirky unsigned things were happening when I tried to cast these directly to doubles below.
+ // it looks redundant, but extracting them to local int16's seemed to fix it.
+ int16_t lat_centi = (int16_t)movement_location.bit.latitude;
+ int16_t lon_centi = (int16_t)movement_location.bit.longitude;
+
+ double lat = (double)lat_centi / 100.0;
+ double lon = (double)lon_centi / 100.0;
+
+ state->daylen = day_length(utc_now.unit.year + WATCH_RTC_REFERENCE_YEAR, utc_now.unit.month, utc_now.unit.day, lon, lat);
+
+ state->result = sun_rise_set(utc_now.unit.year + WATCH_RTC_REFERENCE_YEAR, utc_now.unit.month, utc_now.unit.day, lon, lat, &state->rise, &state->set);
+}
+
+void day_night_percentage_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(day_night_percentage_state_t));
+ day_night_percentage_state_t *state = (day_night_percentage_state_t *)*context_ptr;
+ watch_date_time utc_now = watch_utility_date_time_convert_zone(watch_rtc_get_date_time(), movement_timezone_offsets[settings->bit.time_zone] * 60, 0);
+ recalculate(utc_now, state);
+ }
+}
+
+void day_night_percentage_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
+bool day_night_percentage_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ day_night_percentage_state_t *state = (day_night_percentage_state_t *)context;
+
+ char buf[12];
+ watch_date_time date_time = watch_rtc_get_date_time();
+ watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0);
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ case EVENT_TICK:
+ case EVENT_LOW_ENERGY_UPDATE:
+ if ((utc_now.unit.hour == 0 && utc_now.unit.minute == 0 && utc_now.unit.second == 0) || state->result == -2) {
+ recalculate(utc_now, state);
+ }
+
+ if (state->result == -2) {
+ watch_display_string(" no Loc", 0);
+ break;
+ }
+
+ const char* weekday = watch_utility_get_weekday(date_time);
+ if (state->result != 0) {
+ if (state->result == 1) {
+ watch_clear_indicator(WATCH_INDICATOR_PM);
+ } else {
+ watch_set_indicator(WATCH_INDICATOR_PM);
+ }
+ sprintf(buf, "%s%2dEtrnal", weekday, date_time.unit.day);
+ watch_display_string(buf, 0);
+ } else {
+ double day_hours_decimal = utc_now.unit.hour + (utc_now.unit.minute + (utc_now.unit.second / 60.0)) / 60.0;
+
+ double day_percentage = (24.0 - better_fmod(state->rise - day_hours_decimal, 24.0)) / state->daylen;
+ double night_percentage = (24.0 - better_fmod(state->set - day_hours_decimal, 24.0)) / (24 - state->daylen);
+
+ uint16_t percentage;
+ if (day_percentage > 0.0 && day_percentage < 1.0) {
+ percentage = day_percentage * 10000;
+ watch_clear_indicator(WATCH_INDICATOR_PM);
+ } else {
+ percentage = night_percentage * 10000;
+ watch_set_indicator(WATCH_INDICATOR_PM);
+ }
+ if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
+ if (!watch_tick_animation_is_running()) watch_start_tick_animation(500);
+ sprintf(buf, "%s%2d %02d ", weekday, date_time.unit.day, percentage / 100);
+ } else {
+ sprintf(buf, "%s%2d %04d", weekday, date_time.unit.day, percentage);
+ }
+ watch_display_string(buf, 0);
+ }
+
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+
+ return true;
+}
+
+void day_night_percentage_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
diff --git a/movement/watch_faces/clock/day_night_percentage_face.h b/movement/watch_faces/clock/day_night_percentage_face.h
new file mode 100644
index 00000000..d172c748
--- /dev/null
+++ b/movement/watch_faces/clock/day_night_percentage_face.h
@@ -0,0 +1,66 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Wesley Aptekar-Cassels
+ *
+ * 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 DAY_NIGHT_PERCENTAGE_FACE_H_
+#define DAY_NIGHT_PERCENTAGE_FACE_H_
+
+#include "movement.h"
+
+/*
+ * Day/night percentage face
+ *
+ * Shows the percentage of the way through the day/night the current time is.
+ *
+ * The time digits show the percentage of the way through the day/night it is,
+ * with decimals in the smaller seconds digits. If the day or night will last
+ * for a full 24 hours, the text "Etrnal" is displayed instead of a percentage.
+ * The "PM" indicator is set when it is currently nighttime. The weekday and
+ * day digits display the weekday and day, as one would expect.
+ *
+ * This face does not currently offer any configuration. You must set the
+ * location register with some other face.
+ */
+
+typedef struct {
+ int result; // -1, 0, 1: result from sun_rise_set, -2: no location set
+ double rise;
+ double set;
+ double daylen;
+} day_night_percentage_state_t;
+
+void day_night_percentage_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void day_night_percentage_face_activate(movement_settings_t *settings, void *context);
+bool day_night_percentage_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void day_night_percentage_face_resign(movement_settings_t *settings, void *context);
+
+#define day_night_percentage_face ((const watch_face_t){ \
+ day_night_percentage_face_setup, \
+ day_night_percentage_face_activate, \
+ day_night_percentage_face_loop, \
+ day_night_percentage_face_resign, \
+ NULL, \
+})
+
+#endif // DAY_NIGHT_PERCENTAGE_FACE_H_
+
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 fc78b2d8..e9e5e319 100644
--- a/movement/watch_faces/clock/repetition_minute_face.c
+++ b/movement/watch_faces/clock/repetition_minute_face.c
@@ -151,6 +151,8 @@ 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);
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.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 ac9a97b2..fbc2c4b3 100644
--- a/movement/watch_faces/clock/simple_clock_face.c
+++ b/movement/watch_faces/clock/simple_clock_face.c
@@ -136,11 +136,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting
case EVENT_BACKGROUND_TASK:
// uncomment this line to snap back to the clock face when the hour signal sounds:
// movement_move_to_face(state->watch_face_index);
- #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.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.c b/movement/watch_faces/clock/wyoscan_face.c
index fd87b911..66a5d466 100644
--- a/movement/watch_faces/clock/wyoscan_face.c
+++ b/movement/watch_faces/clock/wyoscan_face.c
@@ -60,7 +60,7 @@ a line you've already drawn. It is vaguely top to bottom and counter,
clockwise when possible.
*/
static char *segment_map[] = {
- "AXFEDCBX", // 0
+ "AXFBDEXC", // 0
"BXXXCXXX", // 1
"ABGEXXXD", // 2
"ABGXXXCD", // 3
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.c b/movement/watch_faces/complication/day_one_face.c
index 25ce1c23..27601edc 100644
--- a/movement/watch_faces/complication/day_one_face.c
+++ b/movement/watch_faces/complication/day_one_face.c
@@ -27,24 +27,54 @@
#include "day_one_face.h"
#include "watch.h"
+static const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31};
+
static uint32_t _day_one_face_juliandaynum(uint16_t year, uint16_t month, uint16_t day) {
// from here: https://en.wikipedia.org/wiki/Julian_day#Julian_day_number_calculation
return (1461 * (year + 4800 + (month - 14) / 12)) / 4 + (367 * (month - 2 - 12 * ((month - 14) / 12))) / 12 - (3 * ((year + 4900 + (month - 14) / 12) / 100))/4 + day - 32075;
}
-static void _day_one_face_update(day_one_state_t state) {
+static void _day_one_face_update(day_one_state_t *state) {
char buf[15];
watch_date_time date_time = watch_rtc_get_date_time();
uint32_t julian_date = _day_one_face_juliandaynum(date_time.unit.year + WATCH_RTC_REFERENCE_YEAR, date_time.unit.month, date_time.unit.day);
- uint32_t julian_birthdate = _day_one_face_juliandaynum(state.birth_year, state.birth_month, state.birth_day);
+ uint32_t julian_birthdate = _day_one_face_juliandaynum(state->birth_year, state->birth_month, state->birth_day);
if (julian_date < julian_birthdate) {
- sprintf(buf, "DA %6lu", julian_birthdate - julian_date);
+ sprintf(buf, "DA %6lu", julian_birthdate - julian_date);
} else {
- sprintf(buf, "DA %6lu", julian_date - julian_birthdate);
+ sprintf(buf, "DA %6lu", julian_date - julian_birthdate);
}
watch_display_string(buf, 0);
}
+static void _day_one_face_abort_quick_cycle(day_one_state_t *state) {
+ if (state->quick_cycle) {
+ state->quick_cycle = false;
+ movement_request_tick_frequency(4);
+ }
+}
+
+static void _day_one_face_increment(day_one_state_t *state) {
+ state->birthday_changed = true;
+ switch (state->current_page) {
+ case PAGE_YEAR:
+ state->birth_year = state->birth_year + 1;
+ if (state->birth_year > 2080) state->birth_year = 1900;
+ break;
+ case PAGE_MONTH:
+ state->birth_month = (state->birth_month % 12) + 1;
+ break;
+ case PAGE_DAY:
+ state->birth_day = state->birth_day + 1;
+ if (state->birth_day == 0 || state->birth_day > days_in_month[state->birth_month - 1]) {
+ state->birth_day = 1;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
void day_one_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
(void) settings;
(void) watch_face_index;
@@ -54,7 +84,7 @@ void day_one_face_setup(movement_settings_t *settings, uint8_t watch_face_index,
movement_birthdate_t movement_birthdate = (movement_birthdate_t) watch_get_backup_data(2);
if (movement_birthdate.reg == 0) {
// if birth date is totally blank, set a reasonable starting date. this works well for anyone under 63, but
- // you can keep pressing to go back to 1900; just pass the current year. also picked this date because if you
+ // you can keep pressing to go back to 1900; just pass the year 2080. also picked this date because if you
// set it to 1959-01-02, it counts up from the launch of Luna-1, the first spacecraft to leave the well.
movement_birthdate.bit.year = 1959;
movement_birthdate.bit.month = 1;
@@ -68,11 +98,9 @@ void day_one_face_activate(movement_settings_t *settings, void *context) {
(void) settings;
day_one_state_t *state = (day_one_state_t *)context;
- // stash the current year, useful in birthday setting mode.
- watch_date_time date_time = watch_rtc_get_date_time();
- state->current_year = date_time.unit.year + WATCH_RTC_REFERENCE_YEAR;
- // reset the current page to 0, display days alive.
- state->current_page = 0;
+ state->current_page = PAGE_DISPLAY;
+ state->quick_cycle = false;
+ state->ticks = 0;
// fetch the user's birth date from the birthday register.
movement_birthdate_t movement_birthdate = (movement_birthdate_t) watch_get_backup_data(2);
@@ -85,96 +113,143 @@ bool day_one_face_loop(movement_event_t event, movement_settings_t *settings, vo
(void) settings;
day_one_state_t *state = (day_one_state_t *)context;
- const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31};
- char buf[6];
+ char buf[9];
switch (event.event_type) {
case EVENT_ACTIVATE:
- _day_one_face_update(*state);
+ _day_one_face_update(state);
break;
case EVENT_LOW_ENERGY_UPDATE:
case EVENT_TICK:
- if (state->current_page != 0) {
- // if in settings mode, update whatever the current page is
- switch (state->current_page) {
- case 1:
- watch_display_string("YR ", 0);
- if (event.subsecond % 2) {
- sprintf(buf, "%4d", state->birth_year);
- watch_display_string(buf, 4);
- }
- break;
- case 2:
- watch_display_string("MO ", 0);
- if (event.subsecond % 2) {
- sprintf(buf, "%2d", state->birth_month);
- watch_display_string(buf, 4);
- }
- break;
- case 3:
- watch_display_string("DA ", 0);
- if (event.subsecond % 2) {
- sprintf(buf, "%2d", state->birth_day);
- watch_display_string(buf, 6);
- }
- break;
+ if (state->quick_cycle) {
+ if (watch_get_pin_level(BTN_ALARM)) {
+ _day_one_face_increment(state);
+ } else {
+ _day_one_face_abort_quick_cycle(state);
}
- } else {
+ }
+ switch (state->current_page) {
+ // if in settings mode, update whatever the current page is
+ case PAGE_YEAR:
+ watch_display_string("YR ", 0);
+ if (event.subsecond % 2) {
+ sprintf(buf, "%4d", state->birth_year);
+ watch_display_string(buf, 4);
+ }
+ break;
+ case PAGE_MONTH:
+ watch_display_string("MO ", 0);
+ if (event.subsecond % 2) {
+ sprintf(buf, "%2d", state->birth_month);
+ watch_display_string(buf, 4);
+ }
+ break;
+ case PAGE_DAY:
+ watch_display_string("DA ", 0);
+ if (event.subsecond % 2) {
+ sprintf(buf, "%2d", state->birth_day);
+ watch_display_string(buf, 6);
+ }
+ break;
// otherwise, check if we have to update. the display only needs to change at midnight!
- watch_date_time date_time = watch_rtc_get_date_time();
- if (date_time.unit.hour == 0 && date_time.unit.minute == 0 && date_time.unit.second == 0) {
- _day_one_face_update(*state);
- }
+ case PAGE_DISPLAY: {
+ watch_date_time date_time = watch_rtc_get_date_time();
+ if (date_time.unit.hour == 0 && date_time.unit.minute == 0 && date_time.unit.second == 0) {
+ _day_one_face_update(state);
+ }
+ break;}
+ case PAGE_DATE:
+ if (state->ticks > 0) {
+ state->ticks--;
+ } else {
+ state->current_page = PAGE_DISPLAY;
+ _day_one_face_update(state);
+ }
+ break;
+ default:
+ break;
}
break;
case EVENT_LIGHT_BUTTON_DOWN:
// only illuminate if we're in display mode
- if (state->current_page == 0) movement_illuminate_led();
+ switch (state->current_page) {
+ case PAGE_DISPLAY:
+ // fall through
+ case PAGE_DATE:
+ movement_illuminate_led();
+ break;
+ default:
+ break;
+ }
break;
case EVENT_LIGHT_BUTTON_UP:
// otherwise use the light button to advance settings pages.
- if (state->current_page != 0) {
- // go to next setting page...
- state->current_page = (state->current_page + 1) % 4;
- if (state->current_page == 0) {
- // ...unless we've been pushed back to display mode.
- movement_request_tick_frequency(1);
- // force display since it normally won't update til midnight.
- _day_one_face_update(*state);
- }
+ switch (state->current_page) {
+ case PAGE_YEAR:
+ // fall through
+ case PAGE_MONTH:
+ // fall through
+ case PAGE_DAY:
+ // go to next setting page...
+ state->current_page = (state->current_page + 1) % 4;
+ if (state->current_page == PAGE_DISPLAY) {
+ // ...unless we've been pushed back to display mode.
+ movement_request_tick_frequency(1);
+ // force display since it normally won't update til midnight.
+ _day_one_face_update(state);
+ }
+ break;
+ default:
+ break;
}
break;
case EVENT_ALARM_BUTTON_UP:
// if we are on a settings page, increment whatever value we're setting.
- if (state->current_page != 0) {
- state->birthday_changed = true;
- switch (state->current_page) {
- case 1:
- state->birth_year = state->birth_year + 1;
- if (state->birth_year > state->current_year) state->birth_year = 1900;
- break;
- case 2:
- state->birth_month = (state->birth_month % 12) + 1;
- break;
- case 3:
- state->birth_day = state->birth_day + 1;
- if (state->birth_day == 0 || state->birth_day > days_in_month[state->birth_month - 1]) {
- state->birth_day = 1;
- }
- break;
- }
+ switch (state->current_page) {
+ case PAGE_YEAR:
+ // fall through
+ case PAGE_MONTH:
+ // fall through
+ case PAGE_DAY:
+ _day_one_face_abort_quick_cycle(state);
+ _day_one_face_increment(state);
+ break;
+ case PAGE_DISPLAY:
+ state->current_page = PAGE_DATE;
+ sprintf(buf, "%04d%02d%02d", state->birth_year % 10000, state->birth_month % 100, state->birth_day % 100);
+ watch_display_string(buf, 2);
+ state->ticks = 2;
+ break;
+ default:
+ break;
}
break;
case EVENT_ALARM_LONG_PRESS:
// if we aren't already in settings mode, put us there.
- if (state->current_page == 0) {
- state->current_page++;
- movement_request_tick_frequency(4);
+ switch (state->current_page) {
+ case PAGE_DISPLAY:
+ state->current_page++;
+ movement_request_tick_frequency(4);
+ break;
+ case PAGE_YEAR:
+ // fall through
+ case PAGE_MONTH:
+ // fall through
+ case PAGE_DAY:
+ state->quick_cycle = true;
+ movement_request_tick_frequency(8);
+ break;
+ default:
+ break;
}
break;
+ case EVENT_ALARM_LONG_UP:
+ _day_one_face_abort_quick_cycle(state);
+ break;
case EVENT_TIMEOUT:
+ _day_one_face_abort_quick_cycle(state);
// return home if we're on a settings page (this saves our changes when we resign).
- if (state->current_page != 0) {
+ if (state->current_page != PAGE_DISPLAY) {
movement_move_to_face(0);
}
break;
diff --git a/movement/watch_faces/complication/day_one_face.h b/movement/watch_faces/complication/day_one_face.h
index ab8372bf..9a59e3bb 100644
--- a/movement/watch_faces/complication/day_one_face.h
+++ b/movement/watch_faces/complication/day_one_face.h
@@ -25,18 +25,46 @@
#ifndef DAY_ONE_FACE_H_
#define DAY_ONE_FACE_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.
+ */
+
#include "movement.h"
-// The Day One face is designed to count upwards from the wearer's date of birth. It also functions as an
-// interface for setting the birth date register, which other watch faces can use for various purposes.
+typedef enum {
+ PAGE_DISPLAY,
+ PAGE_YEAR,
+ PAGE_MONTH,
+ PAGE_DAY,
+ PAGE_DATE
+} day_one_page_t;
typedef struct {
- uint8_t current_page;
- uint16_t current_year;
+ day_one_page_t current_page;
uint16_t birth_year;
uint8_t birth_month;
uint8_t birth_day;
bool birthday_changed;
+ bool quick_cycle;
+ uint8_t ticks;
} day_one_state_t;
void day_one_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
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/kitchen_conversions_face.c b/movement/watch_faces/complication/kitchen_conversions_face.c
new file mode 100644
index 00000000..c19e7554
--- /dev/null
+++ b/movement/watch_faces/complication/kitchen_conversions_face.c
@@ -0,0 +1,480 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 PrimmR
+ *
+ * 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 "kitchen_conversions_face.h"
+
+typedef struct
+{
+ char name[6]; // Name to display on selection
+ double conv_factor_uk; // Unit as represented in base units (UK)
+ double conv_factor_us; // Unit as represented in base units (US)
+ int16_t linear_factor; // Addition of constant (For temperatures)
+} unit;
+
+#define TICK_FREQ 4
+
+#define MEASURES_COUNT 3 // Number of different measurement 'types'
+#define WEIGHT 0
+#define TEMP 1
+#define VOL 2
+
+// Names of measurements
+static char measures[MEASURES_COUNT][6] = {"WeIght", " Temp", " VOL"};
+
+// Number of items in each category
+#define WEIGHT_COUNT 4
+#define TEMP_COUNT 3
+#define VOL_COUNT 9
+const uint8_t units_count[4] = {WEIGHT_COUNT, TEMP_COUNT, VOL_COUNT};
+
+static const unit weights[WEIGHT_COUNT] = {
+ {" g", 1., 1., 0}, // BASE
+ {" kg", 1000., 1000, 0},
+ {"Ounce", 28.34952, 28.34952, 0},
+ {" Pound", 453.5924, 453.5924, 0},
+};
+
+static const unit temps[TEMP_COUNT] = {
+ {" # C", 1.8, 1.8, 32},
+ {" # F", 1., 1., 0}, // BASE
+ {"Gas Mk", 25., 25., 250},
+};
+
+static const unit vols[VOL_COUNT] = {
+ {" n&L", 1., 1., 0}, // BASE (ml)
+ {" L", 1000., 1000., 0},
+ {" Fl Oz", 28.41306, 29.57353, 0},
+ {" Tbsp", 17.75816, 14.78677, 0},
+ {" Tsp", 5.919388, 4.928922, 0},
+ {" Cup", 284.1306, 236.5882, 0},
+ {" Pint", 568.2612, 473.1765, 0},
+ {" Quart", 1136.522, 946.353, 0},
+ {"Gallon", 4546.09, 3785.412, 0},
+};
+
+static int8_t calc_success_seq[5] = {BUZZER_NOTE_G6, 10, BUZZER_NOTE_C7, 10, 0};
+static int8_t calc_fail_seq[5] = {BUZZER_NOTE_C7, 10, BUZZER_NOTE_G6, 10, 0};
+
+// Resets all state variables to 0
+static void reset_state(kitchen_conversions_state_t *state, movement_settings_t *settings)
+{
+ state->pg = measurement;
+ state->measurement_i = 0;
+ state->from_i = 0;
+ state->from_is_us = settings->bit.use_imperial_units; // If uses imperial, most likely to be US
+ state->to_i = 0;
+ state->to_is_us = settings->bit.use_imperial_units;
+ state->selection_value = 0;
+ state->selection_index = 0;
+ state->light_held = false;
+}
+
+void kitchen_conversions_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(kitchen_conversions_state_t));
+ memset(*context_ptr, 0, sizeof(kitchen_conversions_state_t));
+ // Do any one-time tasks in here; the inside of this conditional happens only at boot.
+ }
+ // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.
+}
+
+void kitchen_conversions_face_activate(movement_settings_t *settings, void *context)
+{
+ (void)settings;
+ kitchen_conversions_state_t *state = (kitchen_conversions_state_t *)context;
+
+ // Handle any tasks related to your watch face coming on screen.
+ movement_request_tick_frequency(TICK_FREQ);
+
+ reset_state(state, settings);
+}
+
+// Increments index pointer by 1, wrapping
+#define increment_wrapping(index, wrap) ({(index)++; index %= wrap; })
+
+static uint32_t pow_10(uint8_t n)
+{
+ uint32_t result = 1;
+ for (int i = 0; i < n; i++)
+ {
+ result *= 10;
+ }
+ return result;
+}
+
+// Returns correct list of units for the measurement index
+static unit *get_unit_list(uint8_t measurement_i)
+{
+ switch (measurement_i)
+ {
+ case WEIGHT:
+ return (unit *)weights;
+
+ case TEMP:
+ return (unit *)temps;
+
+ case VOL:
+ return (unit *)vols;
+
+ default:
+ return (unit *)weights;
+ }
+}
+
+// Increment digit by 1 in input (wraps)
+static void increment_input(kitchen_conversions_state_t *state)
+{
+ uint8_t digit = state->selection_value / pow_10(DISPLAY_DIGITS - 1 - state->selection_index) % 10;
+ if (digit != 9)
+ {
+ state->selection_value += pow_10(DISPLAY_DIGITS - 1 - state->selection_index);
+ }
+ else
+ {
+ state->selection_value -= 9 * pow_10(DISPLAY_DIGITS - 1 - state->selection_index);
+ }
+}
+
+// Displays the list of units in the selected category
+static void display_units(uint8_t measurement_i, uint8_t list_i)
+{
+ watch_display_string(get_unit_list(measurement_i)[list_i].name, 4);
+}
+
+static void display(kitchen_conversions_state_t *state, movement_settings_t *settings, uint8_t subsec)
+{
+ watch_clear_display();
+
+ switch (state->pg)
+ {
+ case measurement:
+ {
+ watch_display_string("Un", 0);
+ watch_display_string(measures[state->measurement_i], 4);
+ }
+ break;
+
+ case from:
+ display_units(state->measurement_i, state->from_i);
+
+ // Display Fr if non-locale specific, else display locale and F
+ if (state->measurement_i == VOL)
+ {
+ watch_display_string("F", 3);
+
+ char *locale = state->from_is_us ? "A " : "GB";
+ watch_display_string(locale, 0);
+ }
+ else
+ {
+ watch_display_string("Fr", 0);
+ }
+
+ break;
+
+ case to:
+ display_units(state->measurement_i, state->to_i);
+
+ // Display To if non-locale specific, else display locale and T
+ if (state->measurement_i == VOL)
+ {
+ watch_display_string("T", 3);
+
+ char *locale = state->to_is_us ? "A " : "GB";
+ watch_display_string(locale, 0);
+ }
+ else
+ {
+ watch_display_string("To", 0);
+ }
+
+ break;
+
+ case input:
+ {
+ char buf[7];
+ sprintf(buf, "%06lu", state->selection_value);
+ watch_display_string(buf, 4);
+
+ // Only allow ints for Gas Mk
+ if (state->measurement_i == TEMP && state->from_i == 2)
+ {
+ watch_display_string(" ", 8);
+ }
+
+ // Blink digit (on & off) twice a second
+ if (subsec % 2)
+ {
+ watch_display_string(" ", 4 + state->selection_index);
+ }
+
+ watch_display_string("In", 0);
+ }
+ break;
+
+ case result:
+ {
+ unit froms = get_unit_list(state->measurement_i)[state->from_i];
+ unit tos = get_unit_list(state->measurement_i)[state->to_i];
+ // Chooses correct factor for locale
+ double f_conv_factor = state->from_is_us ? froms.conv_factor_us : froms.conv_factor_uk;
+ double t_conv_factor = state->to_is_us ? tos.conv_factor_us : tos.conv_factor_uk;
+ // Converts
+ double to_base = (state->selection_value * f_conv_factor) + 100 * froms.linear_factor;
+ double conversion = ((to_base - 100 * tos.linear_factor) / t_conv_factor);
+
+ // If number too large or too small
+ uint8_t lower_bound = (state->measurement_i == TEMP && state->to_i == 2) ? 100 : 0;
+ if (conversion >= 1000000 || conversion < lower_bound)
+ {
+ watch_set_indicator(WATCH_INDICATOR_BELL);
+ watch_display_string("Err", 5);
+
+ if (settings->bit.button_should_sound)
+ watch_buzzer_play_sequence(calc_fail_seq, NULL);
+ }
+ else
+ {
+ uint32_t rounded = conversion + .5;
+ char buf[7];
+ sprintf(buf, "%6lu", rounded);
+ watch_display_string(buf, 4);
+
+ // Make sure LSDs always filled
+ if (rounded < 10)
+ {
+ watch_display_string("00", 7);
+ }
+ else if (rounded < 100)
+ {
+ watch_display_string("0", 7);
+ }
+
+ if (settings->bit.button_should_sound)
+ watch_buzzer_play_sequence(calc_success_seq, NULL);
+ }
+ watch_display_string("=", 1);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+bool kitchen_conversions_face_loop(movement_event_t event, movement_settings_t *settings, void *context)
+{
+ kitchen_conversions_state_t *state = (kitchen_conversions_state_t *)context;
+
+ switch (event.event_type)
+ {
+ case EVENT_ACTIVATE:
+ // Initial UI
+ display(state, settings, event.subsecond);
+ break;
+ case EVENT_TICK:
+ // Update for blink animation on input
+ if (state->pg == input)
+ {
+ display(state, settings, event.subsecond);
+
+ // Increments input twice a second when light button held
+ if (state->light_held && event.subsecond % 2)
+ increment_input(state);
+ }
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ // Cycles options
+ switch (state->pg)
+ {
+ case measurement:
+ increment_wrapping(state->measurement_i, MEASURES_COUNT);
+ break;
+
+ case from:
+ increment_wrapping(state->from_i, units_count[state->measurement_i]);
+ break;
+
+ case to:
+ increment_wrapping(state->to_i, units_count[state->measurement_i]);
+ break;
+
+ case input:
+ increment_input(state);
+ break;
+
+ default:
+ break;
+ }
+
+ // Light button does nothing on final screen
+ if (state->pg != result)
+ display(state, settings, event.subsecond);
+
+ state->light_held = false;
+
+ break;
+
+ case EVENT_ALARM_BUTTON_UP:
+ // Increments selected digit
+ if (state->pg == input)
+ {
+
+ // Moves between digits in input
+ // Wraps at 6 digits unless gas mark selected
+ if (state->selection_index < (DISPLAY_DIGITS - 1) - 2 * (state->measurement_i == TEMP && state->from_i == 2))
+ {
+ state->selection_index++;
+ }
+ else
+ {
+ state->pg++;
+ display(state, settings, event.subsecond);
+ }
+ }
+ // Moves forward 1 page
+ else
+ {
+ if (state->pg == SCREEN_NUM - 1)
+ {
+ reset_state(state, settings);
+ }
+ else
+ {
+ state->pg++;
+ }
+
+ // Play boop
+ if (settings->bit.button_should_sound)
+ watch_buzzer_play_note(BUZZER_NOTE_C7, 50);
+ }
+
+ display(state, settings, event.subsecond);
+
+ state->light_held = false;
+
+ break;
+
+ case EVENT_ALARM_LONG_PRESS:
+ // Moves backwards through pages, resetting certain values
+ if (state->pg != measurement)
+ {
+ switch (state->pg)
+ {
+ case measurement:
+ state->measurement_i = 0;
+ break;
+
+ case from:
+ state->from_i = 0;
+ state->from_is_us = settings->bit.use_imperial_units;
+ break;
+
+ case to:
+ state->to_i = 0;
+ state->to_is_us = settings->bit.use_imperial_units;
+ break;
+
+ case input:
+ state->selection_index = 0;
+ state->selection_value = 0;
+ break;
+
+ case result:
+ state->selection_index = 0;
+ break;
+
+ default:
+ break;
+ }
+
+ state->pg--;
+ display(state, settings, event.subsecond);
+
+ // Play beep
+ if (settings->bit.button_should_sound)
+ watch_buzzer_play_note(BUZZER_NOTE_C8, 50);
+
+ state->light_held = false;
+ }
+ break;
+
+ case EVENT_LIGHT_LONG_PRESS:
+ // Switch between locales
+ if (state->measurement_i == VOL)
+ {
+ if (state->pg == from)
+ {
+ state->from_is_us = !state->from_is_us;
+ }
+ else if (state->pg == to)
+ {
+ state->to_is_us = !state->to_is_us;
+ }
+
+ if (state->pg == from || state->pg == to)
+ {
+ display(state, settings, event.subsecond);
+
+ // Play bleep
+ if (settings->bit.button_should_sound)
+ watch_buzzer_play_note(BUZZER_NOTE_E7, 50);
+ }
+ }
+
+ // Sets flag to increment input digit when light held
+ if (state->pg == input)
+ state->light_held = true;
+
+ break;
+
+ case EVENT_LIGHT_LONG_UP:
+ state->light_held = false;
+ break;
+
+ case EVENT_TIMEOUT:
+ movement_move_to_face(0);
+ break;
+
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+
+ return true;
+}
+
+void kitchen_conversions_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/kitchen_conversions_face.h b/movement/watch_faces/complication/kitchen_conversions_face.h
new file mode 100644
index 00000000..e732579e
--- /dev/null
+++ b/movement/watch_faces/complication/kitchen_conversions_face.h
@@ -0,0 +1,87 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 PrimmR
+ *
+ * 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 KITCHEN_CONVERSIONS_FACE_H_
+#define KITCHEN_CONVERSIONS_FACE_H_
+
+#include "movement.h"
+
+/*
+ * Kitchen Conversions
+ * A face that allows the user to convert between common kitchen units of measurement
+ *
+ * How to use
+ * ----------
+ * Short press the alarm button to move forward through menus, and long press to move backwards
+ *
+ * Press the light button to cycle through options in the menus
+ *
+ * When inputting a number, the light button moves forward one place and the alarm button increments a digit by one
+ *
+ * To convert between Imperial (GB) and US (A) measurements of volume, hold the light button
+ *
+ */
+
+#define SCREEN_NUM 5
+
+// Names of each page
+typedef enum
+{
+ measurement,
+ from,
+ to,
+ input,
+ result,
+} page_t;
+
+#define DISPLAY_DIGITS 6
+
+// Settings when app is running
+typedef struct
+{
+ page_t pg;
+ uint8_t measurement_i;
+ uint8_t from_i;
+ bool from_is_us;
+ uint8_t to_i;
+ bool to_is_us;
+ uint32_t selection_value;
+ uint8_t selection_index;
+ bool light_held;
+} kitchen_conversions_state_t;
+
+void kitchen_conversions_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr);
+void kitchen_conversions_face_activate(movement_settings_t *settings, void *context);
+bool kitchen_conversions_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void kitchen_conversions_face_resign(movement_settings_t *settings, void *context);
+
+#define kitchen_conversions_face ((const watch_face_t){ \
+ kitchen_conversions_face_setup, \
+ kitchen_conversions_face_activate, \
+ kitchen_conversions_face_loop, \
+ kitchen_conversions_face_resign, \
+ NULL, \
+})
+
+#endif // KITCHEN_CONVERSIONS_FACE_H_
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/simple_coin_flip_face.c b/movement/watch_faces/complication/simple_coin_flip_face.c
new file mode 100644
index 00000000..64431f9d
--- /dev/null
+++ b/movement/watch_faces/complication/simple_coin_flip_face.c
@@ -0,0 +1,139 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Wesley Aptekar-Cassels
+ *
+ * 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.
+ */
+
+#if __EMSCRIPTEN__
+#include <time.h>
+#else
+#include "saml22j18a.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "simple_coin_flip_face.h"
+
+#define SIMPLE_COIN_FLIP_REQUIRE_LONG_PRESS_FOR_REFLIP true
+
+void simple_coin_flip_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(simple_coin_flip_state_t));
+ memset(*context_ptr, 0, sizeof(simple_coin_flip_state_t));
+ }
+}
+
+void simple_coin_flip_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
+static uint32_t get_random(uint32_t max) {
+ #if __EMSCRIPTEN__
+ return rand() % max;
+ #else
+ return arc4random_uniform(max);
+ #endif
+}
+
+static void animation_0() {
+ watch_display_string(" ", 8);
+ watch_set_pixel(0, 3);
+ watch_set_pixel(0, 6);
+}
+
+static void animation_1() {
+ watch_display_string(" ", 8);
+ watch_set_pixel(1, 3);
+ watch_set_pixel(1, 5);
+}
+
+static void animation_2() {
+ watch_display_string(" ", 8);
+ watch_set_pixel(2, 2);
+ watch_set_pixel(2, 4);
+}
+
+bool simple_coin_flip_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ simple_coin_flip_state_t *state = (simple_coin_flip_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ watch_display_string("flip", 5);
+ state->animation_frame = 0;
+ break;
+ case EVENT_TICK:
+ switch (state->animation_frame) {
+ case 0:
+ case 7:
+ return true;
+ case 1:
+ movement_request_tick_frequency(8);
+ watch_display_string(" ", 4);
+ // fallthrough
+ case 5:
+ animation_0();
+ break;
+ case 2:
+ case 4:
+ animation_1();
+ break;
+ case 3:
+ animation_2();
+ break;
+ case 6:
+ movement_request_tick_frequency(1);
+ if (get_random(2)) {
+ watch_display_string("Heads ", 4);
+ } else {
+ watch_display_string(" Tails", 4);
+ }
+ break;
+ }
+ state->animation_frame++;
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ case EVENT_ALARM_BUTTON_UP:
+ if (!SIMPLE_COIN_FLIP_REQUIRE_LONG_PRESS_FOR_REFLIP || state->animation_frame == 0) {
+ state->animation_frame = 1;
+ }
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ case EVENT_LIGHT_LONG_PRESS:
+ state->animation_frame = 1;
+ break;
+ case EVENT_TIMEOUT:
+ movement_move_to_face(0);
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+
+ return true;
+}
+
+void simple_coin_flip_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
diff --git a/movement/watch_faces/complication/simple_coin_flip_face.h b/movement/watch_faces/complication/simple_coin_flip_face.h
new file mode 100644
index 00000000..f5e223b8
--- /dev/null
+++ b/movement/watch_faces/complication/simple_coin_flip_face.h
@@ -0,0 +1,62 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Wesley Aptekar-Cassels
+ *
+ * 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 SIMPLE_COIN_FLIP_FACE_H_
+#define SIMPLE_COIN_FLIP_FACE_H_
+
+#include "movement.h"
+
+/*
+ * A extremely simple coin flip face.
+ *
+ * Press ALARM or LIGHT to flip a coin, after a short animation it will display
+ * "Heads" or "Tails". Long-press to flip again (you can change a #define to
+ * allow a short-press to reflip as well, if you'd like).
+ *
+ * This is for people who want a simpler UI than probability_face or
+ * randonaut_face. While those have more features, this one is more immediately
+ * obvious - useful, for instance, if you are using a coin flip to agree on
+ * something with someone, and want the operation to be clear to someone who
+ * has not had anything explained to them.
+ */
+
+typedef struct {
+ uint8_t animation_frame;
+} simple_coin_flip_state_t;
+
+void simple_coin_flip_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void simple_coin_flip_face_activate(movement_settings_t *settings, void *context);
+bool simple_coin_flip_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void simple_coin_flip_face_resign(movement_settings_t *settings, void *context);
+
+#define simple_coin_flip_face ((const watch_face_t){ \
+ simple_coin_flip_face_setup, \
+ simple_coin_flip_face_activate, \
+ simple_coin_flip_face_loop, \
+ simple_coin_flip_face_resign, \
+ NULL, \
+})
+
+#endif // SIMPLE_COIN_FLIP_FACE_H_
+
diff --git a/movement/watch_faces/complication/solstice_face.c b/movement/watch_faces/complication/solstice_face.c
new file mode 100644
index 00000000..e74f8789
--- /dev/null
+++ b/movement/watch_faces/complication/solstice_face.c
@@ -0,0 +1,236 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Wesley Aptekar-Cassels
+ *
+ * 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>
+#include "watch_utility.h"
+#include "solstice_face.h"
+
+// Find solstice or equinox time in JDE for a given year, via method from Meeus Ch 27
+static double calculate_solstice_equinox(uint16_t year, uint8_t k) {
+ double Y = ((double)year - 2000) / 1000;
+ double approx_terms[4][5] = {
+ {2451623.80984, 365242.37404, 0.05169, -0.00411, -0.00057}, // March equinox
+ {2451716.56767, 365241.62603, 0.00325, 0.00888, -0.00030}, // June solstice
+ {2451810.21715, 365242.01767, -0.11575, 0.00337, 0.00078}, // September equinox
+ {2451900.05952, 365242.74049, -0.06223, -0.00823, 0.00032}, // December solstice
+ };
+ double JDE0 = approx_terms[k][0] + Y * (approx_terms[k][1] + Y * (approx_terms[k][2] + Y * (approx_terms[k][3] + Y * approx_terms[k][4])));
+ double T = (JDE0 - 2451545.0) / 36525;
+ double W = 35999.373 * T - 2.47;
+ double dlambda = 1 + (0.0334 * cos(W * M_PI / 180.0)) + (0.0007 * cos(2 * W * M_PI / 180.0));
+ double correction_terms[25][3] = {
+ {485,324.96,1934.136},
+ {203,337.23,32964.467},
+ {199,342.08,20.186},
+ {182,27.85,445267.112},
+ {156,73.14,45036.886},
+ {136,171.52,22518.443},
+ {77,222.54,65928.934},
+ {74,296.72,3034.906},
+ {70,243.58,9037.513},
+ {58,119.81,33718.147},
+ {52,297.17,150.678},
+ {50,21.02,2281.226},
+ {45,247.54,29929.562},
+ {44,325.15,31555.956},
+ {29,60.93,4443.417},
+ {18,155.12,67555.328},
+ {17,288.79,4562.452},
+ {16,198.04,62894.029},
+ {14,199.76,31436.921},
+ {12,95.39,14577.848},
+ {12,287.11,31931.756},
+ {12,320.81,34777.259},
+ {9,227.73,1222.114},
+ {8,15.45,16859.074},
+ };
+ double S = 0;
+ for (int i = 0; i < 25; i++) {
+ S += correction_terms[i][0] * cos((correction_terms[i][1] + correction_terms[i][2] * T) * M_PI / 180.0);
+ }
+ double JDE = JDE0 + (0.00001 * S) / dlambda;
+
+ return JDE;
+}
+
+// Convert JDE to Gergorian datetime as per Meeus Ch 7
+static watch_date_time jde_to_date_time(double JDE) {
+ double tmp = JDE + 0.5;
+ double Z = floor(tmp);
+ double F = fmod(tmp, 1);
+ double A;
+ if (Z < 2299161) {
+ A = Z;
+ } else {
+ double alpha = floor((Z - 1867216.25) / 36524.25);
+ A = Z + 1 + alpha - floor(alpha / 4);
+ }
+ double B = A + 1524;
+ double C = floor((B - 122.1) / 365.25);
+ double D = floor(365.25 * C);
+ double E = floor((B - D) / 30.6001);
+ double day = B - D - floor(30.6001 * E) + F;
+ double month;
+ if (E < 14) {
+ month = E - 1;
+ } else {
+ month = E - 13;
+ }
+ double year;
+ if (month > 2) {
+ year = C - 4716;
+ } else {
+ year = C - 4715;
+ }
+
+ double hours = fmod(day, 1) * 24;
+ double minutes = fmod(hours, 1) * 60;
+ double seconds = fmod(minutes, 1) * 60;
+
+ watch_date_time result = {.unit = {
+ floor(seconds),
+ floor(minutes),
+ floor(hours),
+ floor(day),
+ floor(month),
+ floor(year - 2020)
+ }};
+
+ return result;
+}
+
+static void calculate_datetimes(solstice_state_t *state, movement_settings_t *settings) {
+ for (int i = 0; i < 4; i++) {
+ // TODO: handle DST changes
+ state->datetimes[i] = jde_to_date_time(calculate_solstice_equinox(2020 + state->year, i) + (movement_timezone_offsets[settings->bit.time_zone] / (60.0*24.0)));
+ }
+}
+
+void solstice_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(solstice_state_t));
+ solstice_state_t *state = (solstice_state_t *)*context_ptr;
+
+ watch_date_time now = watch_rtc_get_date_time();
+ state->year = now.unit.year;
+ state->index = 0;
+ calculate_datetimes(state, settings);
+
+ uint32_t now_unix = watch_utility_date_time_to_unix_time(now, 0);
+ for (int i = 0; i < 4; i++) {
+ if (state->index == 0 && watch_utility_date_time_to_unix_time(state->datetimes[i], 0) > now_unix) {
+ state->index = i;
+ }
+ }
+ }
+}
+
+void solstice_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
+static void show_main_screen(solstice_state_t *state) {
+ char buf[11];
+ watch_date_time date_time = state->datetimes[state->index];
+ sprintf(buf, " %2d %2d%02d", date_time.unit.year + 20, date_time.unit.month, date_time.unit.day);
+ watch_display_string(buf, 0);
+}
+
+static void show_date_time(movement_settings_t *settings, solstice_state_t *state) {
+ char buf[11];
+ watch_date_time date_time = state->datetimes[state->index];
+ if (!settings->bit.clock_mode_24h) {
+ 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;
+ }
+ 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_set_colon();
+ watch_display_string(buf, 0);
+}
+
+bool solstice_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ solstice_state_t *state = (solstice_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ALARM_LONG_PRESS:
+ show_date_time(settings, state);
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ if (state->index == 0) {
+ if (state->year == 0) {
+ break;
+ }
+ state->year--;
+ state->index = 3;
+ calculate_datetimes(state, settings);
+ } else {
+ state->index--;
+ }
+ show_main_screen(state);
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ state->index++;
+ if (state->index > 3) {
+ if (state->year == 83) {
+ break;
+ }
+ state->year++;
+ state->index = 0;
+ calculate_datetimes(state, settings);
+ }
+ show_main_screen(state);
+ break;
+ case EVENT_ALARM_LONG_UP:
+ watch_clear_colon();
+ watch_clear_indicator(WATCH_INDICATOR_PM);
+ show_main_screen(state);
+ break;
+ case EVENT_ACTIVATE:
+ show_main_screen(state);
+ break;
+ case EVENT_TIMEOUT:
+ movement_move_to_face(0);
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+
+ return true;
+}
+
+void solstice_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
diff --git a/movement/watch_faces/complication/solstice_face.h b/movement/watch_faces/complication/solstice_face.h
new file mode 100644
index 00000000..ec537c09
--- /dev/null
+++ b/movement/watch_faces/complication/solstice_face.h
@@ -0,0 +1,64 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Wesley Aptekar-Cassels
+ *
+ * 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 SOLSTICE_FACE_H_
+#define SOLSTICE_FACE_H_
+
+#include "movement.h"
+
+/*
+ * A face for telling the dates and times of solstices and equinoxes
+ *
+ * It shows the upcoming solstice or equinox by default. The upper right number
+ * is the year, and the bottom numbers are the date in MMDD format. Use the
+ * alarm / light buttons to go forwards / backwards in time. Long press the
+ * alarm button to show the time of the event, including what weekday it is on,
+ * in your local timezone (DST is not handled).
+ *
+ * Supports the years 2020 - 2083. The calculations are reasonably accurate for
+ * years between 1000 and 3000, but limitations in the sensor watch libraries
+ * (which could easily be worked around) prevent making use of that.
+ */
+
+typedef struct {
+ watch_date_time datetimes[4];
+ uint8_t year;
+ uint8_t index;
+} solstice_state_t;
+
+void solstice_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void solstice_face_activate(movement_settings_t *settings, void *context);
+bool solstice_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void solstice_face_resign(movement_settings_t *settings, void *context);
+
+#define solstice_face ((const watch_face_t){ \
+ solstice_face_setup, \
+ solstice_face_activate, \
+ solstice_face_loop, \
+ solstice_face_resign, \
+ NULL, \
+})
+
+#endif // SOLSTICE_FACE_H_
+
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.c b/movement/watch_faces/complication/tomato_face.c
index 698301e1..3d46ba94 100644
--- a/movement/watch_faces/complication/tomato_face.c
+++ b/movement/watch_faces/complication/tomato_face.c
@@ -84,8 +84,10 @@ static void tomato_draw(tomato_state_t *state) {
sec = 0;
break;
}
- sprintf(buf, "TO %c%2d%02d%2d", kind, min, sec, state->done_count);
- watch_display_string(buf, 0);
+ if (state->visible) {
+ sprintf(buf, "TO %c%2d%02d%2d", kind, min, sec, state->done_count);
+ watch_display_string(buf, 0);
+ }
}
static void tomato_reset(tomato_state_t *state) {
@@ -116,6 +118,7 @@ void tomato_face_setup(movement_settings_t *settings, uint8_t watch_face_index,
state->mode=tomato_ready;
state->kind= tomato_focus;
state->done_count = 0;
+ state->visible = true;
}
}
@@ -127,6 +130,7 @@ void tomato_face_activate(movement_settings_t *settings, void *context) {
watch_set_indicator(WATCH_INDICATOR_BELL);
}
watch_set_colon();
+ state->visible = true;
}
bool tomato_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
@@ -184,6 +188,8 @@ bool tomato_face_loop(movement_event_t event, movement_settings_t *settings, voi
}
void tomato_face_resign(movement_settings_t *settings, void *context) {
+ tomato_state_t *state = (tomato_state_t *)context;
+ state->visible = false;
(void) settings;
(void) context;
}
diff --git a/movement/watch_faces/complication/tomato_face.h b/movement/watch_faces/complication/tomato_face.h
index 5404ad11..25f7db0e 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 {
@@ -44,6 +64,7 @@ typedef struct {
tomato_mode mode;
tomato_kind kind;
uint8_t done_count;
+ bool visible;
} tomato_state_t;
void tomato_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
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..37dd08ef 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>
@@ -272,7 +245,6 @@ static void value_increase(int16_t delta) {
nanosec_state.correction_cadence = (delta > 0) ? 1 : 20;
break;
}
- nanosec_state.correction_profile = (nanosec_state.correction_profile + delta) % nanosec_profile_count;
break;
case 6: // Aging
nanosec_state.aging_ppm_pa += delta;
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.c b/movement/watch_faces/settings/preferences_face.c
index b0e328b3..c96e8d1f 100644
--- a/movement/watch_faces/settings/preferences_face.c
+++ b/movement/watch_faces/settings/preferences_face.c
@@ -136,22 +136,22 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings
watch_display_string(" Never", 4);
break;
case 1:
- watch_display_string("1 hour", 4);
+ watch_display_string("10n&in", 4);
break;
case 2:
- watch_display_string("2 hour", 4);
+ watch_display_string("1 hour", 4);
break;
case 3:
- watch_display_string("6 hour", 4);
+ watch_display_string("2 hour", 4);
break;
case 4:
- watch_display_string("12 hr", 4);
+ watch_display_string("6 hour", 4);
break;
case 5:
- watch_display_string(" 1 day", 4);
+ watch_display_string("12 hr", 4);
break;
case 6:
- watch_display_string(" 2 day", 4);
+ watch_display_string(" 1 day", 4);
break;
case 7:
watch_display_string(" 7 day", 4);
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/save_load_face.c b/movement/watch_faces/settings/save_load_face.c
new file mode 100644
index 00000000..7b4be4d8
--- /dev/null
+++ b/movement/watch_faces/settings/save_load_face.c
@@ -0,0 +1,152 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Wesley Aptekar-Cassels
+ *
+ * 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 "save_load_face.h"
+#include "filesystem.h"
+
+static void save(save_load_state_t *state) {
+ savefile_t savefile = {
+ 1,
+ watch_get_backup_data(0),
+ watch_get_backup_data(1),
+ watch_get_backup_data(2),
+ watch_get_backup_data(3),
+ watch_get_backup_data(4),
+ watch_get_backup_data(5),
+ watch_get_backup_data(6),
+ watch_get_backup_data(7),
+ watch_rtc_get_date_time(),
+ };
+ state->slot[state->index] = savefile;
+ char filename[23];
+ sprintf(filename, "save_load_face_%d.bin", state->index);
+ filesystem_write_file(filename, (char*)&savefile, sizeof(savefile_t));
+}
+
+static void load(save_load_state_t *state, movement_settings_t *settings) {
+ watch_store_backup_data(state->slot[state->index].b0, 0);
+ settings->reg = state->slot[state->index].b0;
+ watch_store_backup_data(state->slot[state->index].b1, 1);
+ watch_store_backup_data(state->slot[state->index].b2, 2);
+ watch_store_backup_data(state->slot[state->index].b3, 3);
+ watch_store_backup_data(state->slot[state->index].b4, 4);
+ watch_store_backup_data(state->slot[state->index].b5, 5);
+ watch_store_backup_data(state->slot[state->index].b6, 6);
+ watch_store_backup_data(state->slot[state->index].b7, 7);
+}
+
+static void load_saves_to_state(save_load_state_t *state) {
+ for (uint8_t i = 0; i < SAVE_LOAD_SLOTS; i++) {
+ char filename[23];
+ sprintf(filename, "save_load_face_%d.bin", i);
+ if (filesystem_get_file_size(filename) != sizeof(savefile_t)) {
+ state->slot[i].version = 0;
+ continue;
+ }
+ filesystem_read_file(filename, (char*)&state->slot[i], sizeof(savefile_t));
+ if (state->slot[i].version != 1) {
+ state->slot[i].version = 0;
+ }
+ }
+}
+
+void save_load_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(save_load_state_t));
+ memset(*context_ptr, 0, sizeof(save_load_state_t));
+ }
+}
+
+void save_load_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ save_load_state_t *state = (save_load_state_t *)context;
+ state->index = 0;
+ state->update_timeout = 0;
+ load_saves_to_state(state);
+}
+
+static void update_display(save_load_state_t *state) {
+ char buf[11];
+ sprintf(buf, "SL %1d", state->index);
+ watch_display_string(buf, 0);
+
+ if (state->slot[state->index].version) {
+ sprintf(buf, "%02d%02d%02d", state->slot[state->index].rtc.unit.year + 20, state->slot[state->index].rtc.unit.month, state->slot[state->index].rtc.unit.day);
+ watch_display_string(buf, 4);
+ } else {
+ watch_display_string("no dat", 4);
+ }
+}
+
+bool save_load_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ save_load_state_t *state = (save_load_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ update_display(state);
+ break;
+ case EVENT_TICK:
+ if (state->update_timeout) {
+ if (!--state->update_timeout) {
+ update_display(state);
+ }
+ }
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ state->index = (state->index + 1) % SAVE_LOAD_SLOTS;
+ update_display(state);
+ break;
+ case EVENT_LIGHT_LONG_PRESS:
+ save(state);
+ watch_display_string("Saved ", 4);
+ state->update_timeout = 3;
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ if (state->slot[state->index].version) {
+ load(state, settings);
+ watch_display_string("Loaded", 4);
+ state->update_timeout = 3;
+ }
+ break;
+ case EVENT_TIMEOUT:
+ movement_move_to_face(0);
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+
+ return true;
+}
+
+void save_load_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/settings/save_load_face.h b/movement/watch_faces/settings/save_load_face.h
new file mode 100644
index 00000000..fc155317
--- /dev/null
+++ b/movement/watch_faces/settings/save_load_face.h
@@ -0,0 +1,82 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Wesley Aptekar-Cassels
+ *
+ * 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 SAVE_LOAD_FACE_H_
+#define SAVE_LOAD_FACE_H_
+
+#include "movement.h"
+#include "watch_rtc.h"
+
+/*
+ * Save/Load face
+ *
+ * This allows you to save your settings (including location, birthday, etc) to
+ * LFS, which is not wiped when firmware is updated, and then load them again.
+ * It provides multiple save slots (four by default).
+ *
+ * Press ALARM to cycle through save slots. Long press LIGHT to save to the
+ * current slot. Long press ALARM to load from the current slot. The date that
+ * a save was taken is shown in YYMMDD format , or "no dat" if the slot is
+ * empty. The index of the current slot is displayed in the upper right corner.
+ *
+ * While the time that a save was taken is recorded, it is not currently
+ * restored.
+ */
+
+typedef struct savefile {
+ uint8_t version;
+ uint32_t b0;
+ uint32_t b1;
+ uint32_t b2;
+ uint32_t b3;
+ uint32_t b4;
+ uint32_t b5;
+ uint32_t b6;
+ uint32_t b7;
+ watch_date_time rtc;
+} savefile_t;
+
+#define SAVE_LOAD_SLOTS 4
+
+typedef struct {
+ uint8_t index;
+ uint8_t update_timeout;
+ savefile_t slot[SAVE_LOAD_SLOTS];
+} save_load_state_t;
+
+void save_load_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void save_load_face_activate(movement_settings_t *settings, void *context);
+bool save_load_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void save_load_face_resign(movement_settings_t *settings, void *context);
+
+#define save_load_face ((const watch_face_t){ \
+ save_load_face_setup, \
+ save_load_face_activate, \
+ save_load_face_loop, \
+ save_load_face_resign, \
+ NULL, \
+})
+
+#endif // SAVE_LOAD_FACE_H_
+
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
index 385c611a..b74e539c 100644
--- a/utils/movement_bulk_installer/standard-red.uf2
+++ b/utils/movement_bulk_installer/standard-red.uf2
Binary files differ
diff --git a/watch-library/hardware/watch/watch_buzzer.c b/watch-library/hardware/watch/watch_buzzer.c
index 18fb4db0..2dce8d23 100644
--- a/watch-library/hardware/watch/watch_buzzer.c
+++ b/watch-library/hardware/watch/watch_buzzer.c
@@ -23,6 +23,7 @@
*/
#include "watch_buzzer.h"
+#include "watch_private_buzzer.h"
#include "../../../watch-library/hardware/include/saml22j18a.h"
#include "../../../watch-library/hardware/include/component/tc.h"
#include "../../../watch-library/hardware/hri/hri_tc_l22.h"
diff --git a/watch-library/hardware/watch/watch_rtc.c b/watch-library/hardware/watch/watch_rtc.c
index 881e2575..93cb9f1c 100644
--- a/watch-library/hardware/watch/watch_rtc.c
+++ b/watch-library/hardware/watch/watch_rtc.c
@@ -84,7 +84,7 @@ void watch_rtc_register_periodic_callback(ext_irq_cb_t callback, uint8_t frequen
if (__builtin_popcount(frequency) != 1) return;
// this left-justifies the period in a 32-bit integer.
- uint32_t tmp = frequency << 24;
+ uint32_t tmp = (frequency & 0xFF) << 24;
// now we can count the leading zeroes to get the value we need.
// 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);
@@ -99,7 +99,7 @@ void watch_rtc_register_periodic_callback(ext_irq_cb_t callback, uint8_t frequen
void watch_rtc_disable_periodic_callback(uint8_t frequency) {
if (__builtin_popcount(frequency) != 1) return;
- uint8_t per_n = __builtin_clz(frequency << 24);
+ uint8_t per_n = __builtin_clz((frequency & 0xFF) << 24);
RTC->MODE2.INTENCLR.reg = 1 << per_n;
}
diff --git a/watch-library/shared/watch/watch_buzzer.h b/watch-library/shared/watch/watch_buzzer.h
index 7ba9a52e..4c39475c 100644
--- a/watch-library/shared/watch/watch_buzzer.h
+++ b/watch-library/shared/watch/watch_buzzer.h
@@ -175,6 +175,8 @@ extern const uint16_t NotePeriods[108];
*/
void watch_buzzer_play_sequence(int8_t *note_sequence, void (*callback_on_end)(void));
+uint16_t sequence_length(int8_t *sequence);
+
/** @brief Aborts a playing sequence.
*/
void watch_buzzer_abort_sequence(void);
diff --git a/watch-library/shared/watch/watch_private_buzzer.c b/watch-library/shared/watch/watch_private_buzzer.c
index 0618f425..def54a46 100644
--- a/watch-library/shared/watch/watch_private_buzzer.c
+++ b/watch-library/shared/watch/watch_private_buzzer.c
@@ -23,6 +23,13 @@
*/
#include "driver_init.h"
-// note: the buzzer uses a 1 MHz clock. these values were determined by dividing 1,000,000 by the target frequency.
-// i.e. for a 440 Hz tone (A4 on the piano), 1MHz/440Hz = 2273
-const uint16_t NotePeriods[108] = {18182,17161,16197,15288,14430,13620,12857,12134,11453,10811,10204,9631,9091,8581,8099,7645,7216,6811,6428,6068,5727,5405,5102,4816,4545,4290,4050,3822,3608,3405,3214,3034,2863,2703,2551,2408,2273,2145,2025,1911,1804,1703,1607,1517,1432,1351,1276,1204,1136,1073,1012,956,902,851,804,758,716,676,638,602,568,536,506,478,451,426,402,379,358,338,319,301,284,268,253,239,225,213,201,190,179,169,159,150,142,134,127};
+uint16_t sequence_length(int8_t *sequence) {
+ uint16_t result = 0;
+
+ while (*sequence != 0){
+ result += *(sequence + 1);
+ sequence += 2;
+ }
+
+ return result;
+}
diff --git a/watch-library/shared/watch/watch_private_buzzer.h b/watch-library/shared/watch/watch_private_buzzer.h
new file mode 100644
index 00000000..3017bbb5
--- /dev/null
+++ b/watch-library/shared/watch/watch_private_buzzer.h
@@ -0,0 +1,33 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Wesley Aptekar-Cassels
+ *
+ * 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 _WATCH_PRIVATE_BUZZER_H_INCLUDED
+#define _WATCH_PRIVATE_BUZZER_H_INCLUDED
+
+// note: the buzzer uses a 1 MHz clock. these values were determined by dividing 1,000,000 by the target frequency.
+// i.e. for a 440 Hz tone (A4 on the piano), 1MHz/440Hz = 2273
+const uint16_t NotePeriods[108] = {18182,17161,16197,15288,14430,13620,12857,12134,11453,10811,10204,9631,9091,8581,8099,7645,7216,6811,6428,6068,5727,5405,5102,4816,4545,4290,4050,3822,3608,3405,3214,3034,2863,2703,2551,2408,2273,2145,2025,1911,1804,1703,1607,1517,1432,1351,1276,1204,1136,1073,1012,956,902,851,804,758,716,676,638,602,568,536,506,478,451,426,402,379,358,338,319,301,284,268,253,239,225,213,201,190,179,169,159,150,142,134,127};
+
+uint16_t sequence_length(int8_t *sequence);
+
+#endif
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 &copy; 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 &copy; 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..7ccb8545 100644
--- a/watch-library/simulator/watch/watch_buzzer.c
+++ b/watch-library/simulator/watch/watch_buzzer.c
@@ -23,6 +23,7 @@
*/
#include "watch_buzzer.h"
+#include "watch_private_buzzer.h"
#include "watch_main_loop.h"
#include <emscripten.h>
@@ -152,7 +153,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..2bb6074c 100644
--- a/watch-library/simulator/watch/watch_rtc.c
+++ b/watch-library/simulator/watch/watch_rtc.c
@@ -92,13 +92,12 @@ void watch_rtc_register_periodic_callback(ext_irq_cb_t callback, uint8_t frequen
if (__builtin_popcount(frequency) != 1) return;
// this left-justifies the period in a 32-bit integer.
- uint32_t tmp = frequency << 24;
+ uint32_t tmp = (frequency & 0xFF) << 24;
// now we can count the leading zeroes to get the value we need.
// 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);
@@ -106,7 +105,7 @@ void watch_rtc_register_periodic_callback(ext_irq_cb_t callback, uint8_t frequen
void watch_rtc_disable_periodic_callback(uint8_t frequency) {
if (__builtin_popcount(frequency) != 1) return;
- uint8_t per_n = __builtin_clz(frequency << 24);
+ uint8_t per_n = __builtin_clz((frequency & 0xFF) << 24);
if (tick_callbacks[per_n] != -1) {
emscripten_clear_interval(tick_callbacks[per_n]);
tick_callbacks[per_n] = -1;
@@ -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;
}