Merge branch 'main' into watch-face-solstice

This commit is contained in:
Wesley Aptekar-Cassels 2024-01-21 01:58:20 -05:00 committed by GitHub
commit 29784983b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
123 changed files with 3364 additions and 783 deletions

View file

@ -6,6 +6,9 @@ on:
branches-ignore:
- gh-pages
env:
COLOR: BLUE
jobs:
build:
container:

View file

@ -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

View file

@ -119,6 +119,10 @@ SRCS += \
../watch_faces/clock/decimal_time_face.c \
../watch_faces/clock/wyoscan_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.

View file

@ -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/"

View file

@ -79,6 +79,7 @@ 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 int16_t movement_timeout_inactivity_deadlines[4] = {60, 120, 300, 1800};
movement_event_t event;
const watch_face_t *wf;
const int16_t movement_timezone_offsets[] = {
0, // 0 : 0:00:00 (UTC)
@ -232,7 +233,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);
@ -247,25 +248,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) {
@ -293,25 +294,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;
}
}
@ -414,7 +421,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]);
wf->activate(&movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
event.subsecond = 0;
event.event_type = EVENT_ACTIVATE;
}
@ -434,7 +441,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]);
wf->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;
@ -444,16 +451,20 @@ static void _sleep_mode_app_loop(void) {
}
bool app_loop(void) {
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;
@ -487,18 +498,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;
}
@ -510,9 +527,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);
}
@ -569,8 +593,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;
}
@ -633,13 +662,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.

View file

@ -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);

View file

@ -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
#endif // MOVEMENT_CONFIG_H_

View file

@ -96,6 +96,10 @@
#include "decimal_time_face.h"
#include "wyoscan_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_

View file

@ -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));

View file

@ -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"

View file

@ -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 {

View file

@ -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?

View file

@ -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 {

View file

@ -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;
}

View file

@ -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_

View file

@ -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:

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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 {

View file

@ -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 {

View file

@ -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"

View file

@ -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

View file

@ -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 youll notice that at first launch the day of week indicators are blank.
* Thats 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;

View file

@ -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
*
* and a description of how use it
* 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.
*
* 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 {

View file

@ -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);

View file

@ -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,

View file

@ -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

View file

@ -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, youll 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 Earths moon
* MA - Mars
* JU - Jupiter
* SA - Saturn
* UR - Uranus
* NE - Neptune
*
* Once youve 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"

View file

@ -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 batterys 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 {

View file

@ -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);

View file

@ -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.
}

View file

@ -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_

View file

@ -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

View file

@ -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,

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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 (state->quick_cycle) {
if (watch_get_pin_level(BTN_ALARM)) {
_day_one_face_increment(state);
} else {
_day_one_face_abort_quick_cycle(state);
}
}
switch (state->current_page) {
// 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;
}
} else {
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;

View file

@ -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 youve 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);

View file

@ -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"
/*

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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,

View file

@ -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)

View file

@ -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;

View file

@ -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.
}

View file

@ -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_

View file

@ -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 {

View file

@ -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>

View file

@ -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

View file

@ -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>

View file

@ -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 Earths 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 planets location, and
* after a flashing C (for Calculating), you will be presented with the
* planets 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 {

View file

@ -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];

View file

@ -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;

View file

@ -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 {

View file

@ -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 its 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 {

View file

@ -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;

View file

@ -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 {

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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 }

View file

@ -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,

View file

@ -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;

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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;

View file

@ -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 {

View file

@ -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>

View file

@ -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);

View file

@ -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;

View file

@ -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];
}
}

View file

@ -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 {

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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 {

View file

@ -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"

View file

@ -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 {

View file

@ -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();
}
}

View file

@ -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_

View file

@ -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
//

View file

@ -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-91Ws OEM module, 593.
*
* Also experiments with caret-free UI: One button cycles hours, the other
* minutes, so theres 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 {

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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 {

View file

@ -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 {

View file

@ -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"

View file

@ -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 L22s ADC.
*
* Note that the Simple Clock watch face includes a low battery warning, so you
* dont 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);

View file

@ -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

View file

@ -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>

View file

@ -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"

Some files were not shown because too many files have changed in this diff Show more