Merge remote-tracking branch 'origin/main' into manual_dst_toggle
* origin/main: (119 commits) add an openocd.cfg for openocd 0.12.0 Fix compile errors and warnings in movement.c and shell.c faces/totp: avoid displaying when key is invalid faces/totp: fix error message not displayed bug faces/totp: remove dynamic memory allocation faces/totp: improve memory usage faces: restore simple_clock_face uf2conv: argument to `re.split` should be a rawstring movement: fix unintended timeout short circuiting movement: convert can_sleep an automatic variable faces/pulsometer: remember pulsometer measurement faces/pulsometer: remember pulsometer calibration faces/totp: update copyrights faces/totp: allow moving backwards through codes faces/clock: add 24h only feature faces/clock: update copyrights and credits faces/totp: delete leading underscores faces/totp: rename initializer macro to credential faces/totp: improve TOTP initializer labeling faces/totp: decode secrets when setting up ...
This commit is contained in:
commit
e3d67af604
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -6,6 +6,9 @@ on:
|
|||||||
branches-ignore:
|
branches-ignore:
|
||||||
- gh-pages
|
- gh-pages
|
||||||
|
|
||||||
|
env:
|
||||||
|
COLOR: BLUE
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
container:
|
container:
|
||||||
|
18
make.mk
18
make.mk
@ -62,6 +62,7 @@ CFLAGS += -MD -MP -MT $(BUILD)/$(*F).o -MF $(BUILD)/$(@F).d
|
|||||||
LDFLAGS += -mcpu=cortex-m0plus -mthumb
|
LDFLAGS += -mcpu=cortex-m0plus -mthumb
|
||||||
LDFLAGS += -Wl,--gc-sections
|
LDFLAGS += -Wl,--gc-sections
|
||||||
LDFLAGS += -Wl,--script=$(TOP)/watch-library/hardware/linker/saml22j18.ld
|
LDFLAGS += -Wl,--script=$(TOP)/watch-library/hardware/linker/saml22j18.ld
|
||||||
|
LDFLAGS += -Wl,--print-memory-usage
|
||||||
|
|
||||||
LIBS += -lm
|
LIBS += -lm
|
||||||
|
|
||||||
@ -120,6 +121,7 @@ SRCS += \
|
|||||||
$(TOP)/watch-library/hardware/watch/watch_storage.c \
|
$(TOP)/watch-library/hardware/watch/watch_storage.c \
|
||||||
$(TOP)/watch-library/hardware/watch/watch_deepsleep.c \
|
$(TOP)/watch-library/hardware/watch/watch_deepsleep.c \
|
||||||
$(TOP)/watch-library/hardware/watch/watch_private.c \
|
$(TOP)/watch-library/hardware/watch/watch_private.c \
|
||||||
|
$(TOP)/watch-library/hardware/watch/watch_private_cdc.c \
|
||||||
$(TOP)/watch-library/hardware/watch/watch.c \
|
$(TOP)/watch-library/hardware/watch/watch.c \
|
||||||
$(TOP)/watch-library/hardware/hal/src/hal_atomic.c \
|
$(TOP)/watch-library/hardware/hal/src/hal_atomic.c \
|
||||||
$(TOP)/watch-library/hardware/hal/src/hal_delay.c \
|
$(TOP)/watch-library/hardware/hal/src/hal_delay.c \
|
||||||
@ -207,7 +209,15 @@ ifeq ($(LED), BLUE)
|
|||||||
CFLAGS += -DWATCH_IS_BLUE_BOARD
|
CFLAGS += -DWATCH_IS_BLUE_BOARD
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(LED), RED)
|
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
|
||||||
|
|
||||||
|
ifeq ($(COLOR), RED)
|
||||||
CFLAGS += -DWATCH_INVERT_LED_POLARITY
|
CFLAGS += -DWATCH_INVERT_LED_POLARITY
|
||||||
CFLAGS += -DNO_FREQCORR
|
CFLAGS += -DNO_FREQCORR
|
||||||
endif
|
endif
|
||||||
@ -220,3 +230,9 @@ endif
|
|||||||
ifeq ($(BOARD), OSO-FEAL-A1-00)
|
ifeq ($(BOARD), OSO-FEAL-A1-00)
|
||||||
CFLAGS += -DCRYSTALLESS
|
CFLAGS += -DCRYSTALLESS
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# Build options to customize movement and faces
|
||||||
|
|
||||||
|
ifdef CLOCK_FACE_24H_ONLY
|
||||||
|
CFLAGS += -DCLOCK_FACE_24H_ONLY
|
||||||
|
endif
|
||||||
|
@ -38,4 +38,6 @@ const watch_face_t watch_faces[] = {
|
|||||||
|
|
||||||
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
||||||
|
|
||||||
|
#define SIGNAL_TUNE_DEFAULT
|
||||||
|
|
||||||
#endif // MOVEMENT_CONFIG_H_
|
#endif // MOVEMENT_CONFIG_H_
|
||||||
|
@ -40,4 +40,6 @@ const watch_face_t watch_faces[] = {
|
|||||||
|
|
||||||
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
||||||
|
|
||||||
|
#define SIGNAL_TUNE_DEFAULT
|
||||||
|
|
||||||
#endif // MOVEMENT_CONFIG_H_
|
#endif // MOVEMENT_CONFIG_H_
|
||||||
|
@ -58,4 +58,6 @@ const watch_face_t watch_faces[] = {
|
|||||||
|
|
||||||
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
||||||
|
|
||||||
|
#define SIGNAL_TUNE_DEFAULT
|
||||||
|
|
||||||
#endif // MOVEMENT_CONFIG_H_
|
#endif // MOVEMENT_CONFIG_H_
|
||||||
|
@ -40,4 +40,6 @@ const watch_face_t watch_faces[] = {
|
|||||||
|
|
||||||
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
||||||
|
|
||||||
|
#define SIGNAL_TUNE_DEFAULT
|
||||||
|
|
||||||
#endif // MOVEMENT_CONFIG_H_
|
#endif // MOVEMENT_CONFIG_H_
|
||||||
|
@ -40,4 +40,6 @@ const watch_face_t watch_faces[] = {
|
|||||||
|
|
||||||
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
||||||
|
|
||||||
|
#define SIGNAL_TUNE_DEFAULT
|
||||||
|
|
||||||
#endif // MOVEMENT_CONFIG_H_
|
#endif // MOVEMENT_CONFIG_H_
|
||||||
|
@ -41,4 +41,6 @@ const watch_face_t watch_faces[] = {
|
|||||||
|
|
||||||
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
||||||
|
|
||||||
|
#define SIGNAL_TUNE_DEFAULT
|
||||||
|
|
||||||
#endif // MOVEMENT_CONFIG_H_
|
#endif // MOVEMENT_CONFIG_H_
|
||||||
|
@ -42,4 +42,6 @@ const watch_face_t watch_faces[] = {
|
|||||||
|
|
||||||
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
||||||
|
|
||||||
|
#define SIGNAL_TUNE_DEFAULT
|
||||||
|
|
||||||
#endif // MOVEMENT_CONFIG_H_
|
#endif // MOVEMENT_CONFIG_H_
|
||||||
|
@ -40,4 +40,6 @@ const watch_face_t watch_faces[] = {
|
|||||||
|
|
||||||
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
|
||||||
|
|
||||||
|
#define SIGNAL_TUNE_DEFAULT
|
||||||
|
|
||||||
#endif // MOVEMENT_CONFIG_H_
|
#endif // MOVEMENT_CONFIG_H_
|
||||||
|
@ -100,7 +100,7 @@ static int filesystem_ls(lfs_t *lfs, const char *path) {
|
|||||||
|
|
||||||
printf("%4ld bytes ", info.size);
|
printf("%4ld bytes ", info.size);
|
||||||
|
|
||||||
printf("%s\n", info.name);
|
printf("%s\r\n", info.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
err = lfs_dir_close(lfs, &dir);
|
err = lfs_dir_close(lfs, &dir);
|
||||||
@ -117,11 +117,11 @@ bool filesystem_init(void) {
|
|||||||
// reformat if we can't mount the filesystem
|
// reformat if we can't mount the filesystem
|
||||||
// this should only happen on the first boot
|
// this should only happen on the first boot
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
printf("Ignore that error! Formatting filesystem...\n");
|
printf("Ignore that error! Formatting filesystem...\r\n");
|
||||||
err = lfs_format(&lfs, &cfg);
|
err = lfs_format(&lfs, &cfg);
|
||||||
if (err < 0) return false;
|
if (err < 0) return false;
|
||||||
err = lfs_mount(&lfs, &cfg) == LFS_ERR_OK;
|
err = lfs_mount(&lfs, &cfg) == LFS_ERR_OK;
|
||||||
printf("Filesystem mounted with %ld bytes free.\n", filesystem_get_free_space());
|
printf("Filesystem mounted with %ld bytes free.\r\n", filesystem_get_free_space());
|
||||||
}
|
}
|
||||||
|
|
||||||
return err == LFS_ERR_OK;
|
return err == LFS_ERR_OK;
|
||||||
@ -139,7 +139,7 @@ bool filesystem_rm(char *filename) {
|
|||||||
if (filesystem_file_exists(filename)) {
|
if (filesystem_file_exists(filename)) {
|
||||||
return lfs_remove(&lfs, filename) == LFS_ERR_OK;
|
return lfs_remove(&lfs, filename) == LFS_ERR_OK;
|
||||||
} else {
|
} else {
|
||||||
printf("rm: %s: No such file\n", filename);
|
printf("rm: %s: No such file\r\n", filename);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,13 +197,13 @@ static void filesystem_cat(char *filename) {
|
|||||||
char *buf = malloc(info.size + 1);
|
char *buf = malloc(info.size + 1);
|
||||||
filesystem_read_file(filename, buf, info.size);
|
filesystem_read_file(filename, buf, info.size);
|
||||||
buf[info.size] = '\0';
|
buf[info.size] = '\0';
|
||||||
printf("%s\n", buf);
|
printf("%s\r\n", buf);
|
||||||
free(buf);
|
free(buf);
|
||||||
} else {
|
} else {
|
||||||
printf("\n");
|
printf("\r\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printf("cat: %s: No such file\n", filename);
|
printf("cat: %s: No such file\r\n", filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,59 +223,60 @@ bool filesystem_append_file(char *filename, char *text, int32_t length) {
|
|||||||
return lfs_file_close(&lfs, &file) == LFS_ERR_OK;
|
return lfs_file_close(&lfs, &file) == LFS_ERR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void filesystem_process_command(char *line) {
|
int filesystem_cmd_ls(int argc, char *argv[]) {
|
||||||
printf("$ %s", line);
|
if (argc >= 2) {
|
||||||
char *command = strtok(line, " \n");
|
filesystem_ls(&lfs, argv[1]);
|
||||||
|
|
||||||
if (strcmp(command, "ls") == 0) {
|
|
||||||
char *directory = strtok(NULL, " \n");
|
|
||||||
if (directory == NULL) {
|
|
||||||
filesystem_ls(&lfs, "/");
|
|
||||||
} else {
|
|
||||||
printf("usage: ls\n");
|
|
||||||
}
|
|
||||||
} else if (strcmp(command, "cat") == 0) {
|
|
||||||
char *filename = strtok(NULL, " \n");
|
|
||||||
if (filename == NULL) {
|
|
||||||
printf("usage: cat file\n");
|
|
||||||
} else {
|
|
||||||
filesystem_cat(filename);
|
|
||||||
}
|
|
||||||
} else if (strcmp(command, "df") == 0) {
|
|
||||||
printf("free space: %ld bytes\n", filesystem_get_free_space());
|
|
||||||
} else if (strcmp(command, "rm") == 0) {
|
|
||||||
char *filename = strtok(NULL, " \n");
|
|
||||||
if (filename == NULL) {
|
|
||||||
printf("usage: rm file\n");
|
|
||||||
} else {
|
|
||||||
filesystem_rm(filename);
|
|
||||||
}
|
|
||||||
} else if (strcmp(command, "echo") == 0) {
|
|
||||||
char *text = malloc(248);
|
|
||||||
memset(text, 0, 248);
|
|
||||||
size_t pos = 0;
|
|
||||||
char *word = strtok(NULL, " \n");
|
|
||||||
while (strcmp(word, ">") && strcmp(word, ">>")) {
|
|
||||||
sprintf(text + pos, "%s ", word);
|
|
||||||
pos += strlen(word) + 1;
|
|
||||||
word = strtok(NULL, " \n");
|
|
||||||
if (word == NULL) break;
|
|
||||||
}
|
|
||||||
text[strlen(text) - 1] = 0;
|
|
||||||
char *filename = strtok(NULL, " \n");
|
|
||||||
if (filename == NULL) {
|
|
||||||
printf("usage: echo text > file\n");
|
|
||||||
} else if (strchr(filename, '/') || strchr(filename, '\\')) {
|
|
||||||
printf("subdirectories are not supported\n");
|
|
||||||
} else if (!strcmp(word, ">")) {
|
|
||||||
filesystem_write_file(filename, text, strlen(text));
|
|
||||||
filesystem_append_file(filename, "\n", 1);
|
|
||||||
} else if (!strcmp(word, ">>")) {
|
|
||||||
filesystem_append_file(filename, text, strlen(text));
|
|
||||||
filesystem_append_file(filename, "\n", 1);
|
|
||||||
}
|
|
||||||
free(text);
|
|
||||||
} else {
|
} else {
|
||||||
printf("%s: command not found\n", command);
|
filesystem_ls(&lfs, "/");
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int filesystem_cmd_cat(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
filesystem_cat(argv[1]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int filesystem_cmd_df(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
printf("free space: %ld bytes\r\n", filesystem_get_free_space());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int filesystem_cmd_rm(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
filesystem_rm(argv[1]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int filesystem_cmd_echo(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
|
||||||
|
char *line = argv[1];
|
||||||
|
size_t line_len = strlen(line);
|
||||||
|
if (line[0] == '"' || line[0] == '\'') {
|
||||||
|
line++;
|
||||||
|
line_len -= 2;
|
||||||
|
line[line_len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strchr(argv[3], '/')) {
|
||||||
|
printf("subdirectories are not supported\r\n");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(argv[2], ">")) {
|
||||||
|
filesystem_write_file(argv[3], line, line_len);
|
||||||
|
filesystem_append_file(argv[3], "\n", 1);
|
||||||
|
} else if (!strcmp(argv[2], ">>")) {
|
||||||
|
filesystem_append_file(argv[3], line, line_len);
|
||||||
|
filesystem_append_file(argv[3], "\n", 1);
|
||||||
|
} else {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -96,9 +96,10 @@ bool filesystem_write_file(char *filename, char *text, int32_t length);
|
|||||||
*/
|
*/
|
||||||
bool filesystem_append_file(char *filename, char *text, int32_t length);
|
bool filesystem_append_file(char *filename, char *text, int32_t length);
|
||||||
|
|
||||||
/** @brief Handles the interactive file browser when Movement is plugged in to USB.
|
int filesystem_cmd_ls(int argc, char *argv[]);
|
||||||
* @param line The command that the user typed into the serial console.
|
int filesystem_cmd_cat(int argc, char *argv[]);
|
||||||
*/
|
int filesystem_cmd_df(int argc, char *argv[]);
|
||||||
void filesystem_process_command(char *line);
|
int filesystem_cmd_rm(int argc, char *argv[]);
|
||||||
|
int filesystem_cmd_echo(int argc, char *argv[]);
|
||||||
|
|
||||||
#endif // FILESYSTEM_H_
|
#endif // FILESYSTEM_H_
|
||||||
|
@ -49,7 +49,10 @@ SRCS += \
|
|||||||
../../littlefs/lfs_util.c \
|
../../littlefs/lfs_util.c \
|
||||||
../movement.c \
|
../movement.c \
|
||||||
../filesystem.c \
|
../filesystem.c \
|
||||||
|
../shell.c \
|
||||||
|
../shell_cmd_list.c \
|
||||||
../watch_faces/clock/simple_clock_face.c \
|
../watch_faces/clock/simple_clock_face.c \
|
||||||
|
../watch_faces/clock/clock_face.c \
|
||||||
../watch_faces/clock/world_clock_face.c \
|
../watch_faces/clock/world_clock_face.c \
|
||||||
../watch_faces/clock/beats_face.c \
|
../watch_faces/clock/beats_face.c \
|
||||||
../watch_faces/clock/weeknumber_clock_face.c \
|
../watch_faces/clock/weeknumber_clock_face.c \
|
||||||
@ -118,6 +121,14 @@ SRCS += \
|
|||||||
../watch_faces/complication/flashlight_face.c \
|
../watch_faces/complication/flashlight_face.c \
|
||||||
../watch_faces/clock/decimal_time_face.c \
|
../watch_faces/clock/decimal_time_face.c \
|
||||||
../watch_faces/clock/wyoscan_face.c \
|
../watch_faces/clock/wyoscan_face.c \
|
||||||
|
../watch_faces/settings/save_load_face.c \
|
||||||
|
../watch_faces/clock/day_night_percentage_face.c \
|
||||||
|
../watch_faces/complication/simple_coin_flip_face.c \
|
||||||
|
../watch_faces/complication/solstice_face.c \
|
||||||
|
../watch_faces/complication/couch_to_5k_face.c \
|
||||||
|
../watch_faces/clock/minute_repeater_decimal_face.c \
|
||||||
|
../watch_faces/complication/tuning_tones_face.c \
|
||||||
|
../watch_faces/complication/kitchen_conversions_face.c \
|
||||||
# New watch faces go above this line.
|
# 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.
|
# Leave this line at the bottom of the file; it has all the targets for making your project.
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
fw_dir="firmware/download"
|
fw_dir="firmware/download"
|
||||||
sim_dir="firmware/simulate"
|
sim_dir="firmware/simulate"
|
||||||
colors=("green" "blue")
|
colors=("green" "blue" "red")
|
||||||
variants=("standard" "backer" "alt_time" "deep_space_now" "focus" "the_athlete" "the_backpacker" "the_stargazer")
|
variants=("standard" "backer" "alt_time" "deep_space_now" "focus" "the_athlete" "the_backpacker" "the_stargazer")
|
||||||
|
|
||||||
if [ -d "$fw_dir" ] ; then
|
if [ -d "$fw_dir" ] ; then
|
||||||
@ -21,12 +21,12 @@ do
|
|||||||
for color in "${colors[@]}"
|
for color in "${colors[@]}"
|
||||||
do
|
do
|
||||||
COLOR=$(echo "$color" | tr '[:lower:]' '[:upper:]')
|
COLOR=$(echo "$color" | tr '[:lower:]' '[:upper:]')
|
||||||
make clean
|
make COLOR=$COLOR clean
|
||||||
make LED=$COLOR FIRMWARE=$VARIANT
|
make COLOR=$COLOR FIRMWARE=$VARIANT
|
||||||
mv "build/watch.uf2" "$fw_dir/$variant-$color.uf2"
|
mv "build/watch.uf2" "$fw_dir/$variant-$color.uf2"
|
||||||
done
|
done
|
||||||
rm -rf ./build-sim
|
rm -rf ./build-sim
|
||||||
emmake make FIRMWARE=$VARIANT
|
emmake make COLOR=GREEN FIRMWARE=$VARIANT
|
||||||
mkdir "$sim_dir/$variant/"
|
mkdir "$sim_dir/$variant/"
|
||||||
mv "build-sim/watch.wasm" "$sim_dir/$variant/"
|
mv "build-sim/watch.wasm" "$sim_dir/$variant/"
|
||||||
mv "build-sim/watch.js" "$sim_dir/$variant/"
|
mv "build-sim/watch.js" "$sim_dir/$variant/"
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
#include "watch.h"
|
#include "watch.h"
|
||||||
#include "filesystem.h"
|
#include "filesystem.h"
|
||||||
#include "movement.h"
|
#include "movement.h"
|
||||||
|
#include "shell.h"
|
||||||
|
|
||||||
#ifndef MOVEMENT_FIRMWARE
|
#ifndef MOVEMENT_FIRMWARE
|
||||||
#include "movement_config.h"
|
#include "movement_config.h"
|
||||||
@ -54,6 +55,8 @@
|
|||||||
#include "alt_fw/deep_space_now.h"
|
#include "alt_fw/deep_space_now.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "movement_custom_signal_tunes.h"
|
||||||
|
|
||||||
// Default to no secondary face behaviour.
|
// Default to no secondary face behaviour.
|
||||||
#ifndef MOVEMENT_SECONDARY_FACE_INDEX
|
#ifndef MOVEMENT_SECONDARY_FACE_INDEX
|
||||||
#define MOVEMENT_SECONDARY_FACE_INDEX 0
|
#define MOVEMENT_SECONDARY_FACE_INDEX 0
|
||||||
@ -67,6 +70,31 @@
|
|||||||
#define MOVEMENT_DEFAULT_GREEN_COLOR 0xF
|
#define MOVEMENT_DEFAULT_GREEN_COLOR 0xF
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Default to 12h mode
|
||||||
|
#ifndef MOVEMENT_DEFAULT_24H_MODE
|
||||||
|
#define MOVEMENT_DEFAULT_24H_MODE false
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Default to mode button sounding on press
|
||||||
|
#ifndef MOVEMENT_DEFAULT_BUTTON_SOUND
|
||||||
|
#define MOVEMENT_DEFAULT_BUTTON_SOUND true
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Default to switch back to main watch face after 60 seconds
|
||||||
|
#ifndef MOVEMENT_DEFAULT_TIMEOUT_INTERVAL
|
||||||
|
#define MOVEMENT_DEFAULT_TIMEOUT_INTERVAL 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Default to switch to low energy mode after 2 hours
|
||||||
|
#ifndef MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL
|
||||||
|
#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Default to 1 second led duration
|
||||||
|
#ifndef MOVEMENT_DEFAULT_LED_DURATION
|
||||||
|
#define MOVEMENT_DEFAULT_LED_DURATION 1
|
||||||
|
#endif
|
||||||
|
|
||||||
#if __EMSCRIPTEN__
|
#if __EMSCRIPTEN__
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#endif
|
#endif
|
||||||
@ -74,7 +102,7 @@
|
|||||||
movement_state_t movement_state;
|
movement_state_t movement_state;
|
||||||
void * watch_face_contexts[MOVEMENT_NUM_FACES];
|
void * watch_face_contexts[MOVEMENT_NUM_FACES];
|
||||||
watch_date_time scheduled_tasks[MOVEMENT_NUM_FACES];
|
watch_date_time scheduled_tasks[MOVEMENT_NUM_FACES];
|
||||||
const int32_t movement_le_inactivity_deadlines[8] = {INT_MAX, 3600, 7200, 21600, 43200, 86400, 172800, 604800};
|
const int32_t movement_le_inactivity_deadlines[8] = {INT_MAX, 600, 3600, 7200, 21600, 43200, 86400, 604800};
|
||||||
const int16_t movement_timeout_inactivity_deadlines[4] = {60, 120, 300, 1800};
|
const int16_t movement_timeout_inactivity_deadlines[4] = {60, 120, 300, 1800};
|
||||||
movement_event_t event;
|
movement_event_t event;
|
||||||
|
|
||||||
@ -331,7 +359,7 @@ bool movement_default_loop_handler(movement_event_t event, movement_settings_t *
|
|||||||
movement_illuminate_led();
|
movement_illuminate_led();
|
||||||
break;
|
break;
|
||||||
case EVENT_MODE_LONG_PRESS:
|
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);
|
movement_move_to_face(MOVEMENT_SECONDARY_FACE_INDEX);
|
||||||
} else {
|
} else {
|
||||||
movement_move_to_face(0);
|
movement_move_to_face(0);
|
||||||
@ -346,25 +374,25 @@ bool movement_default_loop_handler(movement_event_t event, movement_settings_t *
|
|||||||
|
|
||||||
void movement_move_to_face(uint8_t watch_face_index) {
|
void movement_move_to_face(uint8_t watch_face_index) {
|
||||||
movement_state.watch_face_changed = true;
|
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) {
|
void movement_move_to_next_face(void) {
|
||||||
uint16_t face_max;
|
uint16_t face_max;
|
||||||
if (MOVEMENT_SECONDARY_FACE_INDEX) {
|
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 {
|
} else {
|
||||||
face_max = MOVEMENT_NUM_FACES;
|
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) {
|
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) {
|
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) {
|
void movement_schedule_background_task_for_face(uint8_t watch_face_index, watch_date_time date_time) {
|
||||||
@ -392,8 +420,32 @@ void movement_request_wake() {
|
|||||||
_movement_reset_inactivity_countdown();
|
_movement_reset_inactivity_countdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void end_buzzing() {
|
||||||
|
movement_state.is_buzzing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void end_buzzing_and_disable_buzzer(void) {
|
||||||
|
end_buzzing();
|
||||||
|
watch_disable_buzzer();
|
||||||
|
}
|
||||||
|
|
||||||
void movement_play_signal(void) {
|
void movement_play_signal(void) {
|
||||||
watch_buzzer_play_sequence(signal_tune, NULL);
|
void *maybe_disable_buzzer = end_buzzing_and_disable_buzzer;
|
||||||
|
if (watch_is_buzzer_or_led_enabled()) {
|
||||||
|
maybe_disable_buzzer = end_buzzing;
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void movement_play_alarm(void) {
|
void movement_play_alarm(void) {
|
||||||
@ -426,11 +478,13 @@ void app_init(void) {
|
|||||||
|
|
||||||
memset(&movement_state, 0, sizeof(movement_state));
|
memset(&movement_state, 0, sizeof(movement_state));
|
||||||
|
|
||||||
|
movement_state.settings.bit.clock_mode_24h = MOVEMENT_DEFAULT_24H_MODE;
|
||||||
movement_state.settings.bit.led_red_color = MOVEMENT_DEFAULT_RED_COLOR;
|
movement_state.settings.bit.led_red_color = MOVEMENT_DEFAULT_RED_COLOR;
|
||||||
movement_state.settings.bit.led_green_color = MOVEMENT_DEFAULT_GREEN_COLOR;
|
movement_state.settings.bit.led_green_color = MOVEMENT_DEFAULT_GREEN_COLOR;
|
||||||
movement_state.settings.bit.button_should_sound = true;
|
movement_state.settings.bit.button_should_sound = MOVEMENT_DEFAULT_BUTTON_SOUND;
|
||||||
movement_state.settings.bit.le_interval = 1;
|
movement_state.settings.bit.to_interval = MOVEMENT_DEFAULT_TIMEOUT_INTERVAL;
|
||||||
movement_state.settings.bit.led_duration = 1;
|
movement_state.settings.bit.le_interval = MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL;
|
||||||
|
movement_state.settings.bit.led_duration = MOVEMENT_DEFAULT_LED_DURATION;
|
||||||
movement_state.light_ticks = -1;
|
movement_state.light_ticks = -1;
|
||||||
movement_state.alarm_ticks = -1;
|
movement_state.alarm_ticks = -1;
|
||||||
movement_state.next_available_backup_register = 4;
|
movement_state.next_available_backup_register = 4;
|
||||||
@ -495,7 +549,7 @@ void app_setup(void) {
|
|||||||
watch_faces[i].setup(&movement_state.settings, i, &watch_face_contexts[i]);
|
watch_faces[i].setup(&movement_state.settings, i, &watch_face_contexts[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch_faces[movement_state.current_watch_face].activate(&movement_state.settings, watch_face_contexts[movement_state.current_watch_face]);
|
watch_faces[movement_state.current_face_idx].activate(&movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
|
||||||
event.subsecond = 0;
|
event.subsecond = 0;
|
||||||
event.event_type = EVENT_ACTIVATE;
|
event.event_type = EVENT_ACTIVATE;
|
||||||
}
|
}
|
||||||
@ -515,7 +569,7 @@ static void _sleep_mode_app_loop(void) {
|
|||||||
if (movement_state.needs_background_tasks_handled) _movement_handle_background_tasks();
|
if (movement_state.needs_background_tasks_handled) _movement_handle_background_tasks();
|
||||||
|
|
||||||
event.event_type = EVENT_LOW_ENERGY_UPDATE;
|
event.event_type = EVENT_LOW_ENERGY_UPDATE;
|
||||||
watch_faces[movement_state.current_watch_face].loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_watch_face]);
|
watch_faces[movement_state.current_face_idx].loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
|
||||||
|
|
||||||
// if we need to wake immediately, do it!
|
// if we need to wake immediately, do it!
|
||||||
if (movement_state.needs_wake) return;
|
if (movement_state.needs_wake) return;
|
||||||
@ -525,16 +579,20 @@ static void _sleep_mode_app_loop(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool app_loop(void) {
|
bool app_loop(void) {
|
||||||
|
const watch_face_t *wf = &watch_faces[movement_state.current_face_idx];
|
||||||
|
bool woke_up_for_buzzer = false;
|
||||||
if (movement_state.watch_face_changed) {
|
if (movement_state.watch_face_changed) {
|
||||||
if (movement_state.settings.bit.button_should_sound) {
|
if (movement_state.settings.bit.button_should_sound) {
|
||||||
// low note for nonzero case, high note for return to watch_face 0
|
// 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]);
|
wf->resign(&movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
|
||||||
movement_state.current_watch_face = movement_state.next_watch_face;
|
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();
|
watch_clear_display();
|
||||||
movement_request_tick_frequency(1);
|
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.subsecond = 0;
|
||||||
event.event_type = EVENT_ACTIVATE;
|
event.event_type = EVENT_ACTIVATE;
|
||||||
movement_state.watch_face_changed = false;
|
movement_state.watch_face_changed = false;
|
||||||
@ -568,18 +626,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,
|
// _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.
|
// or wake is requested using the movement_request_wake function.
|
||||||
_sleep_mode_app_loop();
|
_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;
|
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.
|
// 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.
|
// need to figure out if there's a better heuristic for determining how we woke up.
|
||||||
app_setup();
|
app_setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool can_sleep = true;
|
// default to being allowed to sleep by the face.
|
||||||
|
bool can_sleep = true;
|
||||||
|
|
||||||
if (event.event_type) {
|
if (event.event_type) {
|
||||||
event.subsecond = movement_state.subsecond;
|
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;
|
event.event_type = EVENT_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -591,9 +655,17 @@ bool app_loop(void) {
|
|||||||
event.event_type = EVENT_TIMEOUT;
|
event.event_type = EVENT_TIMEOUT;
|
||||||
}
|
}
|
||||||
event.subsecond = movement_state.subsecond;
|
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
|
||||||
|
bool can_sleep2 = wf->loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
|
||||||
|
can_sleep = can_sleep && can_sleep2;
|
||||||
event.event_type = EVENT_NONE;
|
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.
|
// ...but if the user has "timeout always" set, give it the boot.
|
||||||
movement_move_to_face(0);
|
movement_move_to_face(0);
|
||||||
}
|
}
|
||||||
@ -619,30 +691,9 @@ bool app_loop(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we are plugged into USB, handle the file browser tasks
|
// if we are plugged into USB, handle the serial shell
|
||||||
if (watch_is_usb_enabled()) {
|
if (watch_is_usb_enabled()) {
|
||||||
char line[256] = {0};
|
shell_task();
|
||||||
#if __EMSCRIPTEN__
|
|
||||||
// This is a terrible hack; ideally this should be handled deeper in the watch library.
|
|
||||||
// Alas, emscripten treats read() as something that should pop up an input box, so I
|
|
||||||
// wasn't able to implement this over there. I sense that this relates to read() being
|
|
||||||
// the wrong way to read data from USB (like we should be using fgets or something), but
|
|
||||||
// until I untangle that, this will have to do.
|
|
||||||
char *received_data = (char*)EM_ASM_INT({
|
|
||||||
var len = lengthBytesUTF8(tx) + 1;
|
|
||||||
var s = _malloc(len);
|
|
||||||
stringToUTF8(tx, s, len);
|
|
||||||
return s;
|
|
||||||
});
|
|
||||||
memcpy(line, received_data, min(255, strlen(received_data)));
|
|
||||||
free(received_data);
|
|
||||||
EM_ASM({
|
|
||||||
tx = "";
|
|
||||||
});
|
|
||||||
#else
|
|
||||||
read(0, line, 256);
|
|
||||||
#endif
|
|
||||||
if (strlen(line)) filesystem_process_command(line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event.subsecond = 0;
|
event.subsecond = 0;
|
||||||
@ -650,8 +701,13 @@ bool app_loop(void) {
|
|||||||
// if the watch face changed, we can't sleep because we need to update the display.
|
// 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 (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 we woke up for the buzzer, stay awake until it's finished.
|
||||||
if (movement_state.is_buzzing || movement_state.light_ticks != -1) can_sleep = false;
|
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;
|
return can_sleep;
|
||||||
}
|
}
|
||||||
|
@ -247,8 +247,8 @@ typedef struct {
|
|||||||
movement_settings_t settings;
|
movement_settings_t settings;
|
||||||
|
|
||||||
// transient properties
|
// transient properties
|
||||||
int16_t current_watch_face;
|
int16_t current_face_idx;
|
||||||
int16_t next_watch_face;
|
int16_t next_face_idx;
|
||||||
bool watch_face_changed;
|
bool watch_face_changed;
|
||||||
bool fast_tick_enabled;
|
bool fast_tick_enabled;
|
||||||
int16_t fast_ticks;
|
int16_t fast_ticks;
|
||||||
|
@ -49,8 +49,50 @@ const watch_face_t watch_faces[] = {
|
|||||||
*/
|
*/
|
||||||
#define MOVEMENT_SECONDARY_FACE_INDEX (MOVEMENT_NUM_FACES - 2) // or (0)
|
#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
|
#define SIGNAL_TUNE_DEFAULT
|
||||||
#include "movement_custom_signal_tunes.h"
|
|
||||||
|
/* Determines the intensity of the led colors
|
||||||
|
* Set a hex value 0-15 with 0x0 being off and 0xF being max intensity
|
||||||
|
*/
|
||||||
|
#define MOVEMENT_DEFAULT_GREEN_COLOR 0xF
|
||||||
|
#define MOVEMENT_DEFAULT_RED_COLOR 0x0
|
||||||
|
|
||||||
|
/* Set to true for 24h mode or false for 12h mode */
|
||||||
|
#define MOVEMENT_DEFAULT_24H_MODE false
|
||||||
|
|
||||||
|
/* Enable or disable the sound on mode button press */
|
||||||
|
#define MOVEMENT_DEFAULT_BUTTON_SOUND true
|
||||||
|
|
||||||
|
/* Set the timeout before switching back to the main watch face
|
||||||
|
* Valid values are:
|
||||||
|
* 0: 60 seconds
|
||||||
|
* 1: 2 minutes
|
||||||
|
* 2: 5 minutes
|
||||||
|
* 3: 30 minutes
|
||||||
|
*/
|
||||||
|
#define MOVEMENT_DEFAULT_TIMEOUT_INTERVAL 0
|
||||||
|
|
||||||
|
/* Set the timeout before switching to low energy mode
|
||||||
|
* Valid values are:
|
||||||
|
* 0: Never
|
||||||
|
* 1: 1 hour
|
||||||
|
* 2: 2 hours
|
||||||
|
* 3: 6 hours
|
||||||
|
* 4: 12 hours
|
||||||
|
* 5: 1 day
|
||||||
|
* 6: 2 days
|
||||||
|
* 7: 7 days
|
||||||
|
*/
|
||||||
|
#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 1
|
||||||
|
|
||||||
|
/* Set the led duration
|
||||||
|
* Valid values are:
|
||||||
|
* 0: No LED
|
||||||
|
* 1: 1 second
|
||||||
|
* 2: 3 seconds
|
||||||
|
* 3: 5 seconds
|
||||||
|
*/
|
||||||
|
#define MOVEMENT_DEFAULT_LED_DURATION 1
|
||||||
|
|
||||||
#endif // MOVEMENT_CONFIG_H_
|
#endif // MOVEMENT_CONFIG_H_
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#define MOVEMENT_FACES_H_
|
#define MOVEMENT_FACES_H_
|
||||||
|
|
||||||
#include "simple_clock_face.h"
|
#include "simple_clock_face.h"
|
||||||
|
#include "clock_face.h"
|
||||||
#include "world_clock_face.h"
|
#include "world_clock_face.h"
|
||||||
#include "preferences_face.h"
|
#include "preferences_face.h"
|
||||||
#include "set_time_face.h"
|
#include "set_time_face.h"
|
||||||
@ -95,6 +96,14 @@
|
|||||||
#include "flashlight_face.h"
|
#include "flashlight_face.h"
|
||||||
#include "decimal_time_face.h"
|
#include "decimal_time_face.h"
|
||||||
#include "wyoscan_face.h"
|
#include "wyoscan_face.h"
|
||||||
|
#include "save_load_face.h"
|
||||||
|
#include "day_night_percentage_face.h"
|
||||||
|
#include "simple_coin_flip_face.h"
|
||||||
|
#include "solstice_face.h"
|
||||||
|
#include "couch_to_5k_face.h"
|
||||||
|
#include "minute_repeater_decimal_face.h"
|
||||||
|
#include "tuning_tones_face.h"
|
||||||
|
#include "kitchen_conversions_face.h"
|
||||||
// New includes go above this line.
|
// New includes go above this line.
|
||||||
|
|
||||||
#endif // MOVEMENT_FACES_H_
|
#endif // MOVEMENT_FACES_H_
|
||||||
|
221
movement/shell.c
Normal file
221
movement/shell.c
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Edward Shin
|
||||||
|
*
|
||||||
|
* 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 "shell.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if __EMSCRIPTEN__
|
||||||
|
#include <emscripten.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "watch.h"
|
||||||
|
#include "shell_cmd_list.h"
|
||||||
|
|
||||||
|
extern shell_command_t g_shell_commands[];
|
||||||
|
extern const size_t g_num_shell_commands;
|
||||||
|
|
||||||
|
#define NEWLINE "\r\n"
|
||||||
|
|
||||||
|
#define SHELL_BUF_SZ (256)
|
||||||
|
#define SHELL_MAX_ARGS (16)
|
||||||
|
#define SHELL_PROMPT "swsh> "
|
||||||
|
|
||||||
|
static char s_buf[SHELL_BUF_SZ] = {0};
|
||||||
|
static size_t s_buf_len = 0;
|
||||||
|
// Pointer to the first invalid byte after the end of input.
|
||||||
|
static char *const s_buf_end = s_buf + SHELL_BUF_SZ;
|
||||||
|
|
||||||
|
static char *prv_skip_whitespace(char *c) {
|
||||||
|
while (c >= s_buf && c < s_buf_end) {
|
||||||
|
if (*c == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if ((!isspace((int) *c)) != 0) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
c++;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *prv_skip_non_whitespace(char *c) {
|
||||||
|
bool in_quote = false;
|
||||||
|
char quote_char;
|
||||||
|
while (c >= s_buf && c < s_buf_end) {
|
||||||
|
if (*c == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
// Basic handling of quoted arguments.
|
||||||
|
// Can't handle recursive quotes. :(
|
||||||
|
if (in_quote || *c == '"' || *c == '\'') {
|
||||||
|
if (!in_quote) {
|
||||||
|
quote_char = *c;
|
||||||
|
in_quote = true;
|
||||||
|
} else if (*c == quote_char) {
|
||||||
|
in_quote = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isspace((int) *c) != 0) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c++;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int prv_handle_command() {
|
||||||
|
char *argv[SHELL_MAX_ARGS] = {0};
|
||||||
|
int argc = 0;
|
||||||
|
|
||||||
|
char *c = &s_buf[0];
|
||||||
|
s_buf[SHELL_BUF_SZ - 1] = '\0';
|
||||||
|
|
||||||
|
while (argc < SHELL_MAX_ARGS) {
|
||||||
|
// Skip contiguous whitespace
|
||||||
|
c = prv_skip_whitespace(c);
|
||||||
|
if (c == NULL) {
|
||||||
|
// Reached end of buffer
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We hit non-whitespace, set argv and argc for this upcoming argument
|
||||||
|
argv[argc++] = c;
|
||||||
|
|
||||||
|
// Skip contiguous non-whitespace
|
||||||
|
c = prv_skip_non_whitespace(c);
|
||||||
|
if (c == NULL) {
|
||||||
|
// Reached end of buffer
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NULL-terminate this arg string and then increment.
|
||||||
|
*(c++) = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match against the command list
|
||||||
|
for (size_t i = 0; i < g_num_shell_commands; i++) {
|
||||||
|
if (!strcasecmp(g_shell_commands[i].name, argv[0])) {
|
||||||
|
// If argc isn't valid for this command, display its help instead.
|
||||||
|
if (((argc - 1) < g_shell_commands[i].min_args) ||
|
||||||
|
((argc - 1) > g_shell_commands[i].max_args)) {
|
||||||
|
if (g_shell_commands[i].help != NULL) {
|
||||||
|
printf(NEWLINE "%s" NEWLINE, g_shell_commands[i].help);
|
||||||
|
}
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
// Call the command's callback
|
||||||
|
if (g_shell_commands[i].cb != NULL) {
|
||||||
|
printf(NEWLINE);
|
||||||
|
int ret = g_shell_commands[i].cb(argc, argv);
|
||||||
|
if (ret == -2) {
|
||||||
|
printf(NEWLINE "%s" NEWLINE, g_shell_commands[i].help);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void shell_task(void) {
|
||||||
|
#if __EMSCRIPTEN__
|
||||||
|
// This is a terrible hack; ideally this should be handled deeper in the watch library.
|
||||||
|
// Alas, emscripten treats read() as something that should pop up an input box, so I
|
||||||
|
// wasn't able to implement this over there. I sense that this relates to read() being
|
||||||
|
// the wrong way to read data from USB (like we should be using fgets or something), but
|
||||||
|
// until I untangle that, this will have to do.
|
||||||
|
char *received_data = (char*)EM_ASM_INT({
|
||||||
|
var len = lengthBytesUTF8(tx) + 1;
|
||||||
|
var s = _malloc(len);
|
||||||
|
stringToUTF8(tx, s, len);
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
s_buf_len = min((SHELL_BUF_SZ - 2), strlen(received_data));
|
||||||
|
memcpy(s_buf, received_data, s_buf_len);
|
||||||
|
free(received_data);
|
||||||
|
s_buf[s_buf_len++] = '\n';
|
||||||
|
s_buf[s_buf_len++] = '\0';
|
||||||
|
prv_handle_command();
|
||||||
|
EM_ASM({
|
||||||
|
tx = "";
|
||||||
|
});
|
||||||
|
#else
|
||||||
|
// Read one character at a time until we run out.
|
||||||
|
while (true) {
|
||||||
|
if (s_buf_len >= (SHELL_BUF_SZ - 1)) {
|
||||||
|
printf(NEWLINE "Command too long, clearing.");
|
||||||
|
printf(NEWLINE SHELL_PROMPT);
|
||||||
|
s_buf_len = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int c = getchar();
|
||||||
|
|
||||||
|
if (c < 0) {
|
||||||
|
// Nothing left to read, we're done.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '\b') {
|
||||||
|
// Handle backspace character.
|
||||||
|
// We need to emit a backspace, overwrite the character on the
|
||||||
|
// screen with a space, and then backspace again to move the cursor.
|
||||||
|
if (s_buf_len > 0) {
|
||||||
|
printf("\b \b");
|
||||||
|
s_buf_len--;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else if (c != '\n' && c != '\r') {
|
||||||
|
// Print regular characters to the screen.
|
||||||
|
putchar(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_buf[s_buf_len] = c;
|
||||||
|
|
||||||
|
if (c == '\n' || c == '\r') {
|
||||||
|
// Newline! Handle the command.
|
||||||
|
s_buf[s_buf_len+1] = '\0';
|
||||||
|
(void) prv_handle_command();
|
||||||
|
s_buf_len = 0;
|
||||||
|
printf(NEWLINE SHELL_PROMPT);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
s_buf_len++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
34
movement/shell.h
Normal file
34
movement/shell.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Edward Shin
|
||||||
|
*
|
||||||
|
* 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 SHELL_H_
|
||||||
|
#define SHELL_H_
|
||||||
|
|
||||||
|
/** @brief Called periodically from the app loop to handle shell commands.
|
||||||
|
* When a full command is complete, parses and executes its matching
|
||||||
|
* callback.
|
||||||
|
*/
|
||||||
|
void shell_task(void);
|
||||||
|
|
||||||
|
#endif
|
159
movement/shell_cmd_list.c
Normal file
159
movement/shell_cmd_list.c
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Edward Shin
|
||||||
|
*
|
||||||
|
* 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 "shell_cmd_list.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "filesystem.h"
|
||||||
|
#include "watch.h"
|
||||||
|
|
||||||
|
static int help_cmd(int argc, char *argv[]);
|
||||||
|
static int flash_cmd(int argc, char *argv[]);
|
||||||
|
static int stress_cmd(int argc, char *argv[]);
|
||||||
|
|
||||||
|
shell_command_t g_shell_commands[] = {
|
||||||
|
{
|
||||||
|
.name = "?",
|
||||||
|
.help = "print command list",
|
||||||
|
.min_args = 0,
|
||||||
|
.max_args = 0,
|
||||||
|
.cb = help_cmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "help",
|
||||||
|
.help = "print command list",
|
||||||
|
.min_args = 0,
|
||||||
|
.max_args = 0,
|
||||||
|
.cb = help_cmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "flash",
|
||||||
|
.help = "reboot to UF2 bootloader",
|
||||||
|
.min_args = 0,
|
||||||
|
.max_args = 0,
|
||||||
|
.cb = flash_cmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "ls",
|
||||||
|
.help = "usage: ls [PATH]",
|
||||||
|
.min_args = 0,
|
||||||
|
.max_args = 1,
|
||||||
|
.cb = filesystem_cmd_ls,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "cat",
|
||||||
|
.help = "usage: cat <PATH>",
|
||||||
|
.min_args = 1,
|
||||||
|
.max_args = 1,
|
||||||
|
.cb = filesystem_cmd_cat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "df",
|
||||||
|
.help = "print filesystem free space",
|
||||||
|
.min_args = 0,
|
||||||
|
.max_args = 0,
|
||||||
|
.cb = filesystem_cmd_df,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "rm",
|
||||||
|
.help = "usage: rm [PATH]",
|
||||||
|
.min_args = 1,
|
||||||
|
.max_args = 1,
|
||||||
|
.cb = filesystem_cmd_rm,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "echo",
|
||||||
|
.help = "usage: echo TEXT {>,>>} FILE",
|
||||||
|
.min_args = 3,
|
||||||
|
.max_args = 3,
|
||||||
|
.cb = filesystem_cmd_echo,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "stress",
|
||||||
|
.help = "test CDC write; usage: stress [LEN] [DELAY_MS]",
|
||||||
|
.min_args = 0,
|
||||||
|
.max_args = 2,
|
||||||
|
.cb = stress_cmd,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t g_num_shell_commands = sizeof(g_shell_commands) / sizeof(shell_command_t);
|
||||||
|
|
||||||
|
static int help_cmd(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
printf("Command List:\r\n");
|
||||||
|
for (size_t i = 0; i < g_num_shell_commands; i++) {
|
||||||
|
printf(" %s\t%s\r\n",
|
||||||
|
g_shell_commands[i].name,
|
||||||
|
(g_shell_commands[i].help) ? g_shell_commands[i].help : ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int flash_cmd(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
watch_reset_to_bootloader();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define STRESS_CMD_MAX_LEN (512)
|
||||||
|
static int stress_cmd(int argc, char *argv[]) {
|
||||||
|
char test_str[STRESS_CMD_MAX_LEN+1] = {0};
|
||||||
|
|
||||||
|
int max_len = 512;
|
||||||
|
int delay = 0;
|
||||||
|
|
||||||
|
if (argc >= 2) {
|
||||||
|
if ((max_len = atoi(argv[1])) == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (max_len > 512) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc >= 3) {
|
||||||
|
delay = atoi(argv[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < max_len; i++) {
|
||||||
|
snprintf(&test_str[i], 2, "%u", (i+1)%10);
|
||||||
|
printf("%u:\t%s\r\n", (i+1), test_str);
|
||||||
|
if (delay > 0) {
|
||||||
|
delay_ms(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
38
movement/shell_cmd_list.h
Normal file
38
movement/shell_cmd_list.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Edward Shin
|
||||||
|
*
|
||||||
|
* 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 SHELL_CMD_LIST_H_
|
||||||
|
#define SHELL_CMD_LIST_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *name; // Name used to invoke the command
|
||||||
|
const char *help; // Help string
|
||||||
|
int8_t min_args; // Minimum number of arguments (_excluding_ the command name)
|
||||||
|
int8_t max_args; // Maximum number of arguments (_excluding_ the command name)
|
||||||
|
int (*cb)(int argc, char *argv[]); // Callback for the command
|
||||||
|
} shell_command_t;
|
||||||
|
|
||||||
|
#endif
|
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
void <#watch_face_name#>_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
|
void <#watch_face_name#>_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
|
||||||
(void) settings;
|
(void) settings;
|
||||||
|
(void) watch_face_index;
|
||||||
if (*context_ptr == NULL) {
|
if (*context_ptr == NULL) {
|
||||||
*context_ptr = malloc(sizeof(<#watch_face_name#>_state_t));
|
*context_ptr = malloc(sizeof(<#watch_face_name#>_state_t));
|
||||||
memset(*context_ptr, 0, sizeof(<#watch_face_name#>_state_t));
|
memset(*context_ptr, 0, sizeof(<#watch_face_name#>_state_t));
|
||||||
|
@ -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 <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "beats_face.h"
|
#include "beats_face.h"
|
||||||
|
@ -1,6 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Wesley Ellis <https://github.com/tahnok>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef BEATS_FACE_H_
|
#ifndef BEATS_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
291
movement/watch_faces/clock/clock_face.c
Normal file
291
movement/watch_faces/clock/clock_face.c
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright © 2021-2023 Joey Castillo <joeycastillo@utexas.edu> <jose.castillo@gmail.com>
|
||||||
|
* Copyright © 2022 David Keck <davidskeck@users.noreply.github.com>
|
||||||
|
* Copyright © 2022 TheOnePerson <a.nebinger@web.de>
|
||||||
|
* Copyright © 2023 Jeremy O'Brien <neutral@fastmail.com>
|
||||||
|
* Copyright © 2023 Mikhail Svarichevsky <3@14.by>
|
||||||
|
* Copyright © 2023 Wesley Aptekar-Cassels <me@wesleyac.com>
|
||||||
|
* Copyright © 2024 Matheus Afonso Martins Moreira <matheus.a.m.moreira@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 "clock_face.h"
|
||||||
|
#include "watch.h"
|
||||||
|
#include "watch_utility.h"
|
||||||
|
#include "watch_private_display.h"
|
||||||
|
|
||||||
|
// 2.2 volts will happen when the battery has maybe 5-10% remaining?
|
||||||
|
// we can refine this later.
|
||||||
|
#ifndef CLOCK_FACE_LOW_BATTERY_VOLTAGE_THRESHOLD
|
||||||
|
#define CLOCK_FACE_LOW_BATTERY_VOLTAGE_THRESHOLD 2200
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CLOCK_FACE_24H_ONLY
|
||||||
|
#define CLOCK_FACE_24H_ONLY 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct {
|
||||||
|
watch_date_time previous;
|
||||||
|
} date_time;
|
||||||
|
uint8_t last_battery_check;
|
||||||
|
uint8_t watch_face_index;
|
||||||
|
bool time_signal_enabled;
|
||||||
|
bool battery_low;
|
||||||
|
} clock_state_t;
|
||||||
|
|
||||||
|
static bool clock_is_in_24h_mode(movement_settings_t *settings) {
|
||||||
|
if (CLOCK_FACE_24H_ONLY) { return true; }
|
||||||
|
return settings->bit.clock_mode_24h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_indicate(WatchIndicatorSegment indicator, bool on) {
|
||||||
|
if (on) {
|
||||||
|
watch_set_indicator(indicator);
|
||||||
|
} else {
|
||||||
|
watch_clear_indicator(indicator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_indicate_alarm(movement_settings_t *settings) {
|
||||||
|
clock_indicate(WATCH_INDICATOR_BELL, settings->bit.alarm_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_indicate_time_signal(clock_state_t *clock) {
|
||||||
|
clock_indicate(WATCH_INDICATOR_SIGNAL, clock->time_signal_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_indicate_24h(movement_settings_t *settings) {
|
||||||
|
clock_indicate(WATCH_INDICATOR_24H, clock_is_in_24h_mode(settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool clock_is_pm(watch_date_time date_time) {
|
||||||
|
return date_time.unit.hour >= 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_indicate_pm(movement_settings_t *settings, watch_date_time date_time) {
|
||||||
|
if (settings->bit.clock_mode_24h) { return; }
|
||||||
|
clock_indicate(WATCH_INDICATOR_PM, clock_is_pm(date_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_indicate_low_available_power(clock_state_t *clock) {
|
||||||
|
// Set the LAP indicator if battery power is low
|
||||||
|
clock_indicate(WATCH_INDICATOR_LAP, clock->battery_low);
|
||||||
|
}
|
||||||
|
|
||||||
|
static watch_date_time clock_24h_to_12h(watch_date_time date_time) {
|
||||||
|
date_time.unit.hour %= 12;
|
||||||
|
|
||||||
|
if (date_time.unit.hour == 0) {
|
||||||
|
date_time.unit.hour = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
return date_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_check_battery_periodically(clock_state_t *clock, watch_date_time date_time) {
|
||||||
|
// check the battery voltage once a day
|
||||||
|
if (date_time.unit.day == clock->last_battery_check) { return; }
|
||||||
|
|
||||||
|
clock->last_battery_check = date_time.unit.day;
|
||||||
|
|
||||||
|
watch_enable_adc();
|
||||||
|
uint16_t voltage = watch_get_vcc_voltage();
|
||||||
|
watch_disable_adc();
|
||||||
|
|
||||||
|
clock->battery_low = voltage < CLOCK_FACE_LOW_BATTERY_VOLTAGE_THRESHOLD;
|
||||||
|
|
||||||
|
clock_indicate_low_available_power(clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_toggle_time_signal(clock_state_t *clock) {
|
||||||
|
clock->time_signal_enabled = !clock->time_signal_enabled;
|
||||||
|
clock_indicate_time_signal(clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_display_all(watch_date_time date_time) {
|
||||||
|
char buf[10 + 1];
|
||||||
|
|
||||||
|
snprintf(
|
||||||
|
buf,
|
||||||
|
sizeof(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, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool clock_display_some(watch_date_time current, watch_date_time previous) {
|
||||||
|
if ((current.reg >> 6) == (previous.reg >> 6)) {
|
||||||
|
// everything before seconds is the same, don't waste cycles setting those segments.
|
||||||
|
|
||||||
|
watch_display_character_lp_seconds('0' + current.unit.second / 10, 8);
|
||||||
|
watch_display_character_lp_seconds('0' + current.unit.second % 10, 9);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else if ((current.reg >> 12) == (previous.reg >> 12)) {
|
||||||
|
// everything before minutes is the same.
|
||||||
|
|
||||||
|
char buf[4 + 1];
|
||||||
|
|
||||||
|
snprintf(
|
||||||
|
buf,
|
||||||
|
sizeof(buf),
|
||||||
|
"%02d%02d",
|
||||||
|
current.unit.minute,
|
||||||
|
current.unit.second
|
||||||
|
);
|
||||||
|
|
||||||
|
watch_display_string(buf, 6);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// other stuff changed; let's do it all.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_display_clock(movement_settings_t *settings, clock_state_t *clock, watch_date_time current) {
|
||||||
|
if (!clock_display_some(current, clock->date_time.previous)) {
|
||||||
|
if (!clock_is_in_24h_mode(settings)) {
|
||||||
|
// if we are in 12 hour mode, do some cleanup.
|
||||||
|
clock_indicate_pm(settings, current);
|
||||||
|
current = clock_24h_to_12h(current);
|
||||||
|
}
|
||||||
|
clock_display_all(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_display_low_energy(watch_date_time date_time) {
|
||||||
|
char buf[10 + 1];
|
||||||
|
|
||||||
|
snprintf(
|
||||||
|
buf,
|
||||||
|
sizeof(buf),
|
||||||
|
"%s%2d%2d%02d ",
|
||||||
|
watch_utility_get_weekday(date_time),
|
||||||
|
date_time.unit.day,
|
||||||
|
date_time.unit.hour,
|
||||||
|
date_time.unit.minute
|
||||||
|
);
|
||||||
|
|
||||||
|
watch_display_string(buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_start_tick_tock_animation(void) {
|
||||||
|
if (!watch_tick_animation_is_running()) {
|
||||||
|
watch_start_tick_animation(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_stop_tick_tock_animation(void) {
|
||||||
|
if (watch_tick_animation_is_running()) {
|
||||||
|
watch_stop_tick_animation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
|
||||||
|
(void) settings;
|
||||||
|
(void) watch_face_index;
|
||||||
|
|
||||||
|
if (*context_ptr == NULL) {
|
||||||
|
*context_ptr = malloc(sizeof(clock_state_t));
|
||||||
|
clock_state_t *state = (clock_state_t *) *context_ptr;
|
||||||
|
state->time_signal_enabled = false;
|
||||||
|
state->watch_face_index = watch_face_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clock_face_activate(movement_settings_t *settings, void *context) {
|
||||||
|
clock_state_t *clock = (clock_state_t *) context;
|
||||||
|
|
||||||
|
clock_stop_tick_tock_animation();
|
||||||
|
|
||||||
|
clock_indicate_time_signal(clock);
|
||||||
|
clock_indicate_alarm(settings);
|
||||||
|
clock_indicate_24h(settings);
|
||||||
|
|
||||||
|
watch_set_colon();
|
||||||
|
|
||||||
|
// this ensures that none of the timestamp fields will match, so we can re-render them all.
|
||||||
|
clock->date_time.previous.reg = 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
|
||||||
|
clock_state_t *state = (clock_state_t *) context;
|
||||||
|
watch_date_time current;
|
||||||
|
|
||||||
|
switch (event.event_type) {
|
||||||
|
case EVENT_LOW_ENERGY_UPDATE:
|
||||||
|
clock_start_tick_tock_animation();
|
||||||
|
clock_display_low_energy(watch_rtc_get_date_time());
|
||||||
|
break;
|
||||||
|
case EVENT_TICK:
|
||||||
|
case EVENT_ACTIVATE:
|
||||||
|
current = watch_rtc_get_date_time();
|
||||||
|
|
||||||
|
clock_display_clock(settings, state, current);
|
||||||
|
|
||||||
|
clock_check_battery_periodically(state, current);
|
||||||
|
|
||||||
|
state->date_time.previous = current;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case EVENT_ALARM_LONG_PRESS:
|
||||||
|
clock_toggle_time_signal(state);
|
||||||
|
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;
|
||||||
|
default:
|
||||||
|
return movement_default_loop_handler(event, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clock_face_resign(movement_settings_t *settings, void *context) {
|
||||||
|
(void) settings;
|
||||||
|
(void) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool clock_face_wants_background_task(movement_settings_t *settings, void *context) {
|
||||||
|
(void) settings;
|
||||||
|
clock_state_t *state = (clock_state_t *) context;
|
||||||
|
if (!state->time_signal_enabled) return false;
|
||||||
|
|
||||||
|
watch_date_time date_time = watch_rtc_get_date_time();
|
||||||
|
|
||||||
|
return date_time.unit.minute == 0;
|
||||||
|
}
|
60
movement/watch_faces/clock/clock_face.h
Normal file
60
movement/watch_faces/clock/clock_face.h
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright © 2021-2022 Joey Castillo <joeycastillo@utexas.edu> <jose.castillo@gmail.com>
|
||||||
|
* Copyright © 2022 Alexsander Akers <me@a2.io>
|
||||||
|
* Copyright © 2022 TheOnePerson <a.nebinger@web.de>
|
||||||
|
* Copyright © 2023 Alex Utter <ooterness@gmail.com>
|
||||||
|
* Copyright © 2024 Matheus Afonso Martins Moreira <matheus.a.m.moreira@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 CLOCK_FACE_H_
|
||||||
|
#define CLOCK_FACE_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CLOCK FACE
|
||||||
|
*
|
||||||
|
* Displays the current local time, just like the original watch.
|
||||||
|
* This is the default display mode in most watch configurations.
|
||||||
|
*
|
||||||
|
* Long-press ALARM to toggle the hourly chime.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
|
void clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||||
|
void clock_face_activate(movement_settings_t *settings, void *context);
|
||||||
|
bool clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
||||||
|
void clock_face_resign(movement_settings_t *settings, void *context);
|
||||||
|
bool clock_face_wants_background_task(movement_settings_t *settings, void *context);
|
||||||
|
|
||||||
|
#define clock_face ((const watch_face_t) { \
|
||||||
|
clock_face_setup, \
|
||||||
|
clock_face_activate, \
|
||||||
|
clock_face_loop, \
|
||||||
|
clock_face_resign, \
|
||||||
|
clock_face_wants_background_task, \
|
||||||
|
})
|
||||||
|
|
||||||
|
#endif // CLOCK_FACE_H_
|
139
movement/watch_faces/clock/day_night_percentage_face.c
Normal file
139
movement/watch_faces/clock/day_night_percentage_face.c
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Wesley Aptekar-Cassels
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include "day_night_percentage_face.h"
|
||||||
|
#include "watch_utility.h"
|
||||||
|
#include "sunriset.h"
|
||||||
|
|
||||||
|
// fmod but handle negatives right
|
||||||
|
static double better_fmod(double x, double y) {
|
||||||
|
return fmod(fmod(x, y) + y, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recalculate(watch_date_time utc_now, day_night_percentage_state_t *state) {
|
||||||
|
movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1);
|
||||||
|
|
||||||
|
if (movement_location.reg == 0) {
|
||||||
|
state->result = -2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weird quirky unsigned things were happening when I tried to cast these directly to doubles below.
|
||||||
|
// it looks redundant, but extracting them to local int16's seemed to fix it.
|
||||||
|
int16_t lat_centi = (int16_t)movement_location.bit.latitude;
|
||||||
|
int16_t lon_centi = (int16_t)movement_location.bit.longitude;
|
||||||
|
|
||||||
|
double lat = (double)lat_centi / 100.0;
|
||||||
|
double lon = (double)lon_centi / 100.0;
|
||||||
|
|
||||||
|
state->daylen = day_length(utc_now.unit.year + WATCH_RTC_REFERENCE_YEAR, utc_now.unit.month, utc_now.unit.day, lon, lat);
|
||||||
|
|
||||||
|
state->result = sun_rise_set(utc_now.unit.year + WATCH_RTC_REFERENCE_YEAR, utc_now.unit.month, utc_now.unit.day, lon, lat, &state->rise, &state->set);
|
||||||
|
}
|
||||||
|
|
||||||
|
void day_night_percentage_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
|
||||||
|
(void) settings;
|
||||||
|
(void) watch_face_index;
|
||||||
|
|
||||||
|
if (*context_ptr == NULL) {
|
||||||
|
*context_ptr = malloc(sizeof(day_night_percentage_state_t));
|
||||||
|
day_night_percentage_state_t *state = (day_night_percentage_state_t *)*context_ptr;
|
||||||
|
watch_date_time utc_now = watch_utility_date_time_convert_zone(watch_rtc_get_date_time(), movement_timezone_offsets[settings->bit.time_zone] * 60, 0);
|
||||||
|
recalculate(utc_now, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void day_night_percentage_face_activate(movement_settings_t *settings, void *context) {
|
||||||
|
(void) settings;
|
||||||
|
(void) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool day_night_percentage_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
|
||||||
|
day_night_percentage_state_t *state = (day_night_percentage_state_t *)context;
|
||||||
|
|
||||||
|
char buf[12];
|
||||||
|
watch_date_time date_time = watch_rtc_get_date_time();
|
||||||
|
watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0);
|
||||||
|
|
||||||
|
switch (event.event_type) {
|
||||||
|
case EVENT_ACTIVATE:
|
||||||
|
case EVENT_TICK:
|
||||||
|
case EVENT_LOW_ENERGY_UPDATE:
|
||||||
|
if ((utc_now.unit.hour == 0 && utc_now.unit.minute == 0 && utc_now.unit.second == 0) || state->result == -2) {
|
||||||
|
recalculate(utc_now, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->result == -2) {
|
||||||
|
watch_display_string(" no Loc", 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* weekday = watch_utility_get_weekday(date_time);
|
||||||
|
if (state->result != 0) {
|
||||||
|
if (state->result == 1) {
|
||||||
|
watch_clear_indicator(WATCH_INDICATOR_PM);
|
||||||
|
} else {
|
||||||
|
watch_set_indicator(WATCH_INDICATOR_PM);
|
||||||
|
}
|
||||||
|
sprintf(buf, "%s%2dEtrnal", weekday, date_time.unit.day);
|
||||||
|
watch_display_string(buf, 0);
|
||||||
|
} else {
|
||||||
|
double day_hours_decimal = utc_now.unit.hour + (utc_now.unit.minute + (utc_now.unit.second / 60.0)) / 60.0;
|
||||||
|
|
||||||
|
double day_percentage = (24.0 - better_fmod(state->rise - day_hours_decimal, 24.0)) / state->daylen;
|
||||||
|
double night_percentage = (24.0 - better_fmod(state->set - day_hours_decimal, 24.0)) / (24 - state->daylen);
|
||||||
|
|
||||||
|
uint16_t percentage;
|
||||||
|
if (day_percentage > 0.0 && day_percentage < 1.0) {
|
||||||
|
percentage = day_percentage * 10000;
|
||||||
|
watch_clear_indicator(WATCH_INDICATOR_PM);
|
||||||
|
} else {
|
||||||
|
percentage = night_percentage * 10000;
|
||||||
|
watch_set_indicator(WATCH_INDICATOR_PM);
|
||||||
|
}
|
||||||
|
if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
|
||||||
|
if (!watch_tick_animation_is_running()) watch_start_tick_animation(500);
|
||||||
|
sprintf(buf, "%s%2d %02d ", weekday, date_time.unit.day, percentage / 100);
|
||||||
|
} else {
|
||||||
|
sprintf(buf, "%s%2d %04d", weekday, date_time.unit.day, percentage);
|
||||||
|
}
|
||||||
|
watch_display_string(buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return movement_default_loop_handler(event, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void day_night_percentage_face_resign(movement_settings_t *settings, void *context) {
|
||||||
|
(void) settings;
|
||||||
|
(void) context;
|
||||||
|
}
|
||||||
|
|
66
movement/watch_faces/clock/day_night_percentage_face.h
Normal file
66
movement/watch_faces/clock/day_night_percentage_face.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Wesley Aptekar-Cassels
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DAY_NIGHT_PERCENTAGE_FACE_H_
|
||||||
|
#define DAY_NIGHT_PERCENTAGE_FACE_H_
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Day/night percentage face
|
||||||
|
*
|
||||||
|
* Shows the percentage of the way through the day/night the current time is.
|
||||||
|
*
|
||||||
|
* The time digits show the percentage of the way through the day/night it is,
|
||||||
|
* with decimals in the smaller seconds digits. If the day or night will last
|
||||||
|
* for a full 24 hours, the text "Etrnal" is displayed instead of a percentage.
|
||||||
|
* The "PM" indicator is set when it is currently nighttime. The weekday and
|
||||||
|
* day digits display the weekday and day, as one would expect.
|
||||||
|
*
|
||||||
|
* This face does not currently offer any configuration. You must set the
|
||||||
|
* location register with some other face.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int result; // -1, 0, 1: result from sun_rise_set, -2: no location set
|
||||||
|
double rise;
|
||||||
|
double set;
|
||||||
|
double daylen;
|
||||||
|
} day_night_percentage_state_t;
|
||||||
|
|
||||||
|
void day_night_percentage_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||||
|
void day_night_percentage_face_activate(movement_settings_t *settings, void *context);
|
||||||
|
bool day_night_percentage_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
||||||
|
void day_night_percentage_face_resign(movement_settings_t *settings, void *context);
|
||||||
|
|
||||||
|
#define day_night_percentage_face ((const watch_face_t){ \
|
||||||
|
day_night_percentage_face_setup, \
|
||||||
|
day_night_percentage_face_activate, \
|
||||||
|
day_night_percentage_face_loop, \
|
||||||
|
day_night_percentage_face_resign, \
|
||||||
|
NULL, \
|
||||||
|
})
|
||||||
|
|
||||||
|
#endif // DAY_NIGHT_PERCENTAGE_FACE_H_
|
||||||
|
|
@ -25,10 +25,8 @@
|
|||||||
#ifndef DECIMAL_TIME_FACE_H_
|
#ifndef DECIMAL_TIME_FACE_H_
|
||||||
#define 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",
|
* 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.
|
* 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/
|
* https://hr.colostate.edu/minute-to-decimal-conversion-chart/
|
||||||
*
|
*
|
||||||
* Many thanks go to Joey Castillo for making this project happen.
|
* Many thanks go to Joey Castillo for making this project happen.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool chime_enabled; // did the user enable hourly chime for this face?
|
bool chime_enabled; // did the user enable hourly chime for this face?
|
||||||
uint8_t features_to_show : 2 ; // what features are to be displayed?
|
uint8_t features_to_show : 2 ; // what features are to be displayed?
|
||||||
|
@ -25,6 +25,32 @@
|
|||||||
#ifndef MARS_TIME_FACE_H_
|
#ifndef MARS_TIME_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
238
movement/watch_faces/clock/minute_repeater_decimal_face.c
Normal file
238
movement/watch_faces/clock/minute_repeater_decimal_face.c
Normal 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;
|
||||||
|
}
|
84
movement/watch_faces/clock/minute_repeater_decimal_face.h
Normal file
84
movement/watch_faces/clock/minute_repeater_decimal_face.h
Normal 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_
|
@ -153,17 +153,7 @@ bool repetition_minute_face_loop(movement_event_t event, movement_settings_t *se
|
|||||||
case EVENT_BACKGROUND_TASK:
|
case EVENT_BACKGROUND_TASK:
|
||||||
// uncomment this line to snap back to the clock face when the hour signal sounds:
|
// uncomment this line to snap back to the clock face when the hour signal sounds:
|
||||||
// movement_move_to_face(state->watch_face_index);
|
// movement_move_to_face(state->watch_face_index);
|
||||||
if (watch_is_buzzer_or_led_enabled()) {
|
movement_play_signal();
|
||||||
// if we are in the foreground, we can just beep.
|
|
||||||
movement_play_signal();
|
|
||||||
} else {
|
|
||||||
// if we were in the background, we need to enable the buzzer peripheral first,
|
|
||||||
watch_enable_buzzer();
|
|
||||||
// beep quickly (this call blocks for 275 ms),
|
|
||||||
movement_play_signal();
|
|
||||||
// and then turn the buzzer peripheral off again.
|
|
||||||
watch_disable_buzzer();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case EVENT_LIGHT_LONG_UP:
|
case EVENT_LIGHT_LONG_UP:
|
||||||
/*
|
/*
|
||||||
|
@ -25,9 +25,9 @@
|
|||||||
#ifndef REPETITION_MINUTE_FACE_H_
|
#ifndef REPETITION_MINUTE_FACE_H_
|
||||||
#define 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
|
* A hopefully useful complication for friendly neighbors in the dark
|
||||||
*
|
*
|
||||||
* Originating from 1676 from reverend and mechanician Edward Barlow, and
|
* 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
|
* before widespread artificial illumination, to allow the time to be determined
|
||||||
* in the dark, and were also used by the visually impaired.
|
* in the dark, and were also used by the visually impaired.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* How to use it :
|
* How to use it :
|
||||||
*
|
*
|
||||||
* Long press the light button to get an auditive reading of the time like so :
|
* 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
|
* Prerequisite : a watch with a working buzzer
|
||||||
*
|
*
|
||||||
* ~ Only in the darkness can you see the stars. - Martin Luther King ~
|
* ~ Only in the darkness can you see the stars. - Martin Luther King ~
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t previous_date_time;
|
uint32_t previous_date_time;
|
||||||
uint8_t last_battery_check;
|
uint8_t last_battery_check;
|
||||||
|
@ -180,17 +180,7 @@ bool simple_clock_bin_led_face_loop(movement_event_t event, movement_settings_t
|
|||||||
case EVENT_BACKGROUND_TASK:
|
case EVENT_BACKGROUND_TASK:
|
||||||
// uncomment this line to snap back to the clock face when the hour signal sounds:
|
// uncomment this line to snap back to the clock face when the hour signal sounds:
|
||||||
// movement_move_to_face(state->watch_face_index);
|
// movement_move_to_face(state->watch_face_index);
|
||||||
if (watch_is_buzzer_or_led_enabled()) {
|
movement_play_signal();
|
||||||
// if we are in the foreground, we can just beep.
|
|
||||||
movement_play_signal();
|
|
||||||
} else {
|
|
||||||
// if we were in the background, we need to enable the buzzer peripheral first,
|
|
||||||
watch_enable_buzzer();
|
|
||||||
// beep quickly (this call blocks for 275 ms),
|
|
||||||
movement_play_signal();
|
|
||||||
// and then turn the buzzer peripheral off again.
|
|
||||||
watch_disable_buzzer();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case EVENT_LIGHT_LONG_PRESS:
|
case EVENT_LIGHT_LONG_PRESS:
|
||||||
if (state->flashing_state == 0) {
|
if (state->flashing_state == 0) {
|
||||||
|
@ -25,9 +25,9 @@
|
|||||||
#ifndef SIIMPLE_CLOCK_BIN_LED_FACE_H_
|
#ifndef SIIMPLE_CLOCK_BIN_LED_FACE_H_
|
||||||
#define 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
|
* A "fork" of the simple clock face, which provides the functionality of showing
|
||||||
* the current time by flashing the LED using binary representation.
|
* the current time by flashing the LED using binary representation.
|
||||||
*
|
*
|
||||||
@ -49,6 +49,8 @@
|
|||||||
* represents 1.
|
* represents 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t previous_date_time;
|
uint32_t previous_date_time;
|
||||||
uint8_t last_battery_check;
|
uint8_t last_battery_check;
|
||||||
|
@ -136,17 +136,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting
|
|||||||
case EVENT_BACKGROUND_TASK:
|
case EVENT_BACKGROUND_TASK:
|
||||||
// uncomment this line to snap back to the clock face when the hour signal sounds:
|
// uncomment this line to snap back to the clock face when the hour signal sounds:
|
||||||
// movement_move_to_face(state->watch_face_index);
|
// movement_move_to_face(state->watch_face_index);
|
||||||
if (watch_is_buzzer_or_led_enabled()) {
|
movement_play_signal();
|
||||||
// if we are in the foreground, we can just beep.
|
|
||||||
movement_play_signal();
|
|
||||||
} else {
|
|
||||||
// if we were in the background, we need to enable the buzzer peripheral first,
|
|
||||||
watch_enable_buzzer();
|
|
||||||
// beep quickly (this call blocks for 275 ms),
|
|
||||||
movement_play_signal();
|
|
||||||
// and then turn the buzzer peripheral off again.
|
|
||||||
watch_disable_buzzer();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return movement_default_loop_handler(event, settings);
|
return movement_default_loop_handler(event, settings);
|
||||||
|
@ -25,6 +25,15 @@
|
|||||||
#ifndef SIMPLE_CLOCK_FACE_H_
|
#ifndef SIMPLE_CLOCK_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -130,17 +130,7 @@ bool weeknumber_clock_face_loop(movement_event_t event, movement_settings_t *set
|
|||||||
case EVENT_BACKGROUND_TASK:
|
case EVENT_BACKGROUND_TASK:
|
||||||
// uncomment this line to snap back to the clock face when the hour signal sounds:
|
// uncomment this line to snap back to the clock face when the hour signal sounds:
|
||||||
// movement_move_to_face(state->watch_face_index);
|
// movement_move_to_face(state->watch_face_index);
|
||||||
if (watch_is_buzzer_or_led_enabled()) {
|
movement_play_signal();
|
||||||
// if we are in the foreground, we can just beep.
|
|
||||||
movement_play_signal();
|
|
||||||
} else {
|
|
||||||
// if we were in the background, we need to enable the buzzer peripheral first,
|
|
||||||
watch_enable_buzzer();
|
|
||||||
// beep quickly (this call blocks for 275 ms),
|
|
||||||
movement_play_signal();
|
|
||||||
// and then turn the buzzer peripheral off again.
|
|
||||||
watch_disable_buzzer();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
movement_default_loop_handler(event, settings);
|
movement_default_loop_handler(event, settings);
|
||||||
|
@ -25,6 +25,14 @@
|
|||||||
#ifndef WEEKNUMBER_CLOCK_FACE_H_
|
#ifndef WEEKNUMBER_CLOCK_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -23,79 +23,6 @@
|
|||||||
* SOFTWARE.
|
* 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 <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "world_clock2_face.h"
|
#include "world_clock2_face.h"
|
||||||
|
@ -26,6 +26,65 @@
|
|||||||
#ifndef WORLD_CLOCK2_FACE_H_
|
#ifndef WORLD_CLOCK2_FACE_H_
|
||||||
#define 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. */
|
/* Number of zones. See movement_timezone_offsets. */
|
||||||
#define NUM_TIME_ZONES 41
|
#define NUM_TIME_ZONES 41
|
||||||
|
|
||||||
|
@ -25,7 +25,29 @@
|
|||||||
#ifndef WORLD_CLOCK_FACE_H_
|
#ifndef WORLD_CLOCK_FACE_H_
|
||||||
#define WORLD_CLOCK_FACE_H_
|
#define WORLD_CLOCK_FACE_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WORLD CLOCK FACE
|
||||||
|
*
|
||||||
|
* The World Clock watch face looks similar to the Simple Clock watch face,
|
||||||
|
* but you’ll notice that at first launch the day of week indicators are blank.
|
||||||
|
* That’s because this watch face does not display the day of the week.
|
||||||
|
* Instead, you may customize these letters to display the name of a time zone
|
||||||
|
* of your choosing.
|
||||||
|
*
|
||||||
|
* To customize this watch face, press and hold the ALARM button. The first
|
||||||
|
* letter in the top row will begin flashing. Press the ALARM button repeatedly
|
||||||
|
* to advance through the available letters in the first slot, then press the
|
||||||
|
* LIGHT button to move to the second letter. Finally, press LIGHT again to move
|
||||||
|
* to the time zone setting, and press ALARM to cycle through the available time
|
||||||
|
* zones. Press LIGHT one last time to return to the world clock display.
|
||||||
|
*
|
||||||
|
* Note that the second slot cannot display all letters or numbers. Also note
|
||||||
|
* that at this time, time zones do not automatically update for daylight saving
|
||||||
|
* time; you will need to manually adjust this field each spring and fall.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "movement.h"
|
#include "movement.h"
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
struct {
|
struct {
|
||||||
uint8_t char_0;
|
uint8_t char_0;
|
||||||
|
@ -60,7 +60,7 @@ a line you've already drawn. It is vaguely top to bottom and counter,
|
|||||||
clockwise when possible.
|
clockwise when possible.
|
||||||
*/
|
*/
|
||||||
static char *segment_map[] = {
|
static char *segment_map[] = {
|
||||||
"AXFEDCBX", // 0
|
"AXFBDEXC", // 0
|
||||||
"BXXXCXXX", // 1
|
"BXXXCXXX", // 1
|
||||||
"ABGEXXXD", // 2
|
"ABGEXXXD", // 2
|
||||||
"ABGXXXCD", // 3
|
"ABGXXXCD", // 3
|
||||||
|
@ -25,15 +25,32 @@
|
|||||||
#ifndef WYOSCAN_FACE_H_
|
#ifndef WYOSCAN_FACE_H_
|
||||||
#define 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
|
#define MAX_ILLUMINATED_SEGMENTS 16
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -25,10 +25,8 @@
|
|||||||
#ifndef ACTIVITY_FACE_H_
|
#ifndef ACTIVITY_FACE_H_
|
||||||
#define 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.
|
* 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
|
* 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
|
* 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.
|
* 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_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||||
void activity_face_activate(movement_settings_t *settings, void *context);
|
void activity_face_activate(movement_settings_t *settings, void *context);
|
||||||
bool activity_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
bool activity_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
||||||
|
@ -22,8 +22,6 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@ -32,31 +30,6 @@
|
|||||||
#include "watch_utility.h"
|
#include "watch_utility.h"
|
||||||
#include "watch_private_display.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 {
|
typedef enum {
|
||||||
alarm_setting_idx_alarm,
|
alarm_setting_idx_alarm,
|
||||||
alarm_setting_idx_day,
|
alarm_setting_idx_day,
|
||||||
|
@ -27,11 +27,34 @@
|
|||||||
#ifndef ALARM_FACE_H_
|
#ifndef ALARM_FACE_H_
|
||||||
#define 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_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
|
#define ALARM_DAY_STATES 11 // no of different day settings
|
||||||
|
@ -25,6 +25,47 @@
|
|||||||
#ifndef ASTRONOMY_FACE_H_
|
#ifndef ASTRONOMY_FACE_H_
|
||||||
#define ASTRONOMY_FACE_H_
|
#define ASTRONOMY_FACE_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ASTRONOMY face
|
||||||
|
*
|
||||||
|
* The Astronomy watch face is among the most complex watch faces in the
|
||||||
|
* Movement collection. It allows you to calculate the locations of celestial
|
||||||
|
* bodies in the sky, as well as distance in astronomical units (or, in the
|
||||||
|
* case of the Moon, distance in kilometers).
|
||||||
|
*
|
||||||
|
* When you arrive at the Astronomy watch face, you’ll see its name (“Astro”)
|
||||||
|
* and an animation of two objects orbiting each other. You will also see “SO”
|
||||||
|
* (for Sol) flashing in the top left. The flashing letters indicate the
|
||||||
|
* currently selected celestial body. Short press Alarm to advance through
|
||||||
|
* the available celestial bodies:
|
||||||
|
*
|
||||||
|
* SO - Sol, the sun
|
||||||
|
* ME - Mercury
|
||||||
|
* VE - Venus
|
||||||
|
* LU - Luna, the Earth’s moon
|
||||||
|
* MA - Mars
|
||||||
|
* JU - Jupiter
|
||||||
|
* SA - Saturn
|
||||||
|
* UR - Uranus
|
||||||
|
* NE - Neptune
|
||||||
|
*
|
||||||
|
* Once you’ve selected the celestial body whose parameters you wish to
|
||||||
|
* calculate, long press the Alarm button and release it. The letter “C” will
|
||||||
|
* flash while the calculation is performed.
|
||||||
|
*
|
||||||
|
* When the calculation is complete, the screen will display the altitude
|
||||||
|
* (“aL”) of the celestial body. You can cycle through the available parameters
|
||||||
|
* with repeated short presses on the Alarm button:
|
||||||
|
*
|
||||||
|
* aL - Altitude (in degrees), the elevation over the horizon. If negative, it is below the horizon.
|
||||||
|
* aZ - Azimuth (in degrees), the cardinal direction relative to true north.
|
||||||
|
* rA - Right Ascension (in hours/minutes/seconds)
|
||||||
|
* dE - Declination (in degrees/minutes/seconds)
|
||||||
|
* di - Distance (the digits in the top right will display either aU for astronomical units, or K for kilometers)
|
||||||
|
*
|
||||||
|
* Long press on the Alarm button to select another celestial body.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "movement.h"
|
#include "movement.h"
|
||||||
#include "astrolib.h"
|
#include "astrolib.h"
|
||||||
|
|
||||||
|
@ -25,6 +25,32 @@
|
|||||||
#ifndef BLINKY_FACE_H_
|
#ifndef BLINKY_FACE_H_
|
||||||
#define BLINKY_FACE_H_
|
#define BLINKY_FACE_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BLINKY LIGHT face
|
||||||
|
*
|
||||||
|
* The blinky light watch face was designed as a tutorial for making a watch
|
||||||
|
* face in Movement, but it actually might be useful to have a blinking light
|
||||||
|
* in a pinch.
|
||||||
|
*
|
||||||
|
* The screen displays the name of the watch face (”BL”), as well as an S at
|
||||||
|
* the top right for slow blink or an F for fast blink. The bottom line selects
|
||||||
|
* the color: green, red or yellow. You can change the speed of the blinking
|
||||||
|
* light by pressing the Alarm button, and change the color with the Light
|
||||||
|
* button. A long press on the Alarm button starts the blinking light, and
|
||||||
|
* another long press stops it.
|
||||||
|
*
|
||||||
|
* Note that this will chew through your battery! The green LED uses about
|
||||||
|
* 450µA at full brightness, which is 45 times the normal power consumption of
|
||||||
|
* the watch. The red LED is an order of magnitude less efficient (4500 µA),
|
||||||
|
* and the yellow setting lights both LEDs, which chews through nearly
|
||||||
|
* 5 milliamperes. This means that one hour of yellow blinking is likely to
|
||||||
|
* eat up between 2 and 3 percent of the battery’s usable life!
|
||||||
|
*
|
||||||
|
* Still, if you need to signal your location to someone in a dark forest,
|
||||||
|
* this watch face could come in handy. Just try to use the green LED as much
|
||||||
|
* as you can.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "movement.h"
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -25,6 +25,17 @@
|
|||||||
#ifndef BREATHING_FACE_H_
|
#ifndef BREATHING_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
void breathing_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
void breathing_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||||
|
267
movement/watch_faces/complication/couch_to_5k_face.c
Normal file
267
movement/watch_faces/complication/couch_to_5k_face.c
Normal 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.
|
||||||
|
}
|
||||||
|
|
87
movement/watch_faces/complication/couch_to_5k_face.h
Normal file
87
movement/watch_faces/complication/couch_to_5k_face.h
Normal 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_
|
||||||
|
|
@ -23,27 +23,12 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "countdown_face.h"
|
#include "countdown_face.h"
|
||||||
#include "watch.h"
|
#include "watch.h"
|
||||||
#include "watch_utility.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 CD_SELECTIONS 3
|
||||||
#define DEFAULT_MINUTES 3
|
#define DEFAULT_MINUTES 3
|
||||||
|
|
||||||
|
@ -22,22 +22,27 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#ifndef COUNTDOWN_FACE_H_
|
#ifndef COUNTDOWN_FACE_H_
|
||||||
#define COUNTDOWN_FACE_H_
|
#define COUNTDOWN_FACE_H_
|
||||||
|
|
||||||
#include "movement.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
A countdown/timer face
|
* COUNTDOWN TIMER face
|
||||||
|
*
|
||||||
Max countdown is 23 hours, 59 minutes and 59 seconds.
|
* Slight extension of the original countdown face by Wesley Ellis.
|
||||||
|
* - Press the light button to enter setting mode and adjust the
|
||||||
Note: we have to prevent the watch from going to deep sleep using
|
* countdown timer.
|
||||||
movement_schedule_background_task() while the timer is running.
|
* - 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 {
|
typedef enum {
|
||||||
cd_paused,
|
cd_paused,
|
||||||
|
@ -25,9 +25,19 @@
|
|||||||
#ifndef COUNTER_FACE_H_
|
#ifndef COUNTER_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
// Counter face is designed to count the number of running laps during excercises.
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t counter_idx;
|
uint8_t counter_idx;
|
||||||
bool beep_on;
|
bool beep_on;
|
||||||
|
@ -20,8 +20,6 @@
|
|||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
* 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
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*
|
|
||||||
* Displays some pre-defined data that you might want to remember. Math constants, birthdays, phone numbers...
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@ -96,12 +94,8 @@ bool databank_face_loop(movement_event_t event, movement_settings_t *settings, v
|
|||||||
case EVENT_ACTIVATE:
|
case EVENT_ACTIVATE:
|
||||||
display();
|
display();
|
||||||
case EVENT_TICK:
|
case EVENT_TICK:
|
||||||
// on activate and tick, if we are animating,
|
|
||||||
break;
|
break;
|
||||||
case EVENT_LIGHT_BUTTON_UP:
|
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;
|
databank_state.current_word = (databank_state.current_word + max_words - 1) % max_words;
|
||||||
display();
|
display();
|
||||||
break;
|
break;
|
||||||
@ -116,8 +110,6 @@ bool databank_face_loop(movement_event_t event, movement_settings_t *settings, v
|
|||||||
display();
|
display();
|
||||||
break;
|
break;
|
||||||
case EVENT_ALARM_BUTTON_UP:
|
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;
|
databank_state.current_word = (databank_state.current_word + 1) % max_words;
|
||||||
display();
|
display();
|
||||||
break;
|
break;
|
||||||
|
@ -25,6 +25,23 @@
|
|||||||
#ifndef DATABANK_FACE_H_
|
#ifndef DATABANK_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
void databank_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
void databank_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||||
|
@ -27,24 +27,54 @@
|
|||||||
#include "day_one_face.h"
|
#include "day_one_face.h"
|
||||||
#include "watch.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) {
|
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
|
// 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;
|
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];
|
char buf[15];
|
||||||
watch_date_time date_time = watch_rtc_get_date_time();
|
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_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) {
|
if (julian_date < julian_birthdate) {
|
||||||
sprintf(buf, "DA %6lu", julian_birthdate - julian_date);
|
sprintf(buf, "DA %6lu", julian_birthdate - julian_date);
|
||||||
} else {
|
} else {
|
||||||
sprintf(buf, "DA %6lu", julian_date - julian_birthdate);
|
sprintf(buf, "DA %6lu", julian_date - julian_birthdate);
|
||||||
}
|
}
|
||||||
watch_display_string(buf, 0);
|
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 day_one_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
|
||||||
(void) settings;
|
(void) settings;
|
||||||
(void) watch_face_index;
|
(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);
|
movement_birthdate_t movement_birthdate = (movement_birthdate_t) watch_get_backup_data(2);
|
||||||
if (movement_birthdate.reg == 0) {
|
if (movement_birthdate.reg == 0) {
|
||||||
// if birth date is totally blank, set a reasonable starting date. this works well for anyone under 63, but
|
// 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.
|
// 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.year = 1959;
|
||||||
movement_birthdate.bit.month = 1;
|
movement_birthdate.bit.month = 1;
|
||||||
@ -68,11 +98,9 @@ void day_one_face_activate(movement_settings_t *settings, void *context) {
|
|||||||
(void) settings;
|
(void) settings;
|
||||||
day_one_state_t *state = (day_one_state_t *)context;
|
day_one_state_t *state = (day_one_state_t *)context;
|
||||||
|
|
||||||
// stash the current year, useful in birthday setting mode.
|
state->current_page = PAGE_DISPLAY;
|
||||||
watch_date_time date_time = watch_rtc_get_date_time();
|
state->quick_cycle = false;
|
||||||
state->current_year = date_time.unit.year + WATCH_RTC_REFERENCE_YEAR;
|
state->ticks = 0;
|
||||||
// reset the current page to 0, display days alive.
|
|
||||||
state->current_page = 0;
|
|
||||||
|
|
||||||
// fetch the user's birth date from the birthday register.
|
// fetch the user's birth date from the birthday register.
|
||||||
movement_birthdate_t movement_birthdate = (movement_birthdate_t) watch_get_backup_data(2);
|
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;
|
(void) settings;
|
||||||
day_one_state_t *state = (day_one_state_t *)context;
|
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[9];
|
||||||
char buf[6];
|
|
||||||
|
|
||||||
switch (event.event_type) {
|
switch (event.event_type) {
|
||||||
case EVENT_ACTIVATE:
|
case EVENT_ACTIVATE:
|
||||||
_day_one_face_update(*state);
|
_day_one_face_update(state);
|
||||||
break;
|
break;
|
||||||
case EVENT_LOW_ENERGY_UPDATE:
|
case EVENT_LOW_ENERGY_UPDATE:
|
||||||
case EVENT_TICK:
|
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
|
// if in settings mode, update whatever the current page is
|
||||||
switch (state->current_page) {
|
case PAGE_YEAR:
|
||||||
case 1:
|
watch_display_string("YR ", 0);
|
||||||
watch_display_string("YR ", 0);
|
if (event.subsecond % 2) {
|
||||||
if (event.subsecond % 2) {
|
sprintf(buf, "%4d", state->birth_year);
|
||||||
sprintf(buf, "%4d", state->birth_year);
|
watch_display_string(buf, 4);
|
||||||
watch_display_string(buf, 4);
|
}
|
||||||
}
|
break;
|
||||||
break;
|
case PAGE_MONTH:
|
||||||
case 2:
|
watch_display_string("MO ", 0);
|
||||||
watch_display_string("MO ", 0);
|
if (event.subsecond % 2) {
|
||||||
if (event.subsecond % 2) {
|
sprintf(buf, "%2d", state->birth_month);
|
||||||
sprintf(buf, "%2d", state->birth_month);
|
watch_display_string(buf, 4);
|
||||||
watch_display_string(buf, 4);
|
}
|
||||||
}
|
break;
|
||||||
break;
|
case PAGE_DAY:
|
||||||
case 3:
|
watch_display_string("DA ", 0);
|
||||||
watch_display_string("DA ", 0);
|
if (event.subsecond % 2) {
|
||||||
if (event.subsecond % 2) {
|
sprintf(buf, "%2d", state->birth_day);
|
||||||
sprintf(buf, "%2d", state->birth_day);
|
watch_display_string(buf, 6);
|
||||||
watch_display_string(buf, 6);
|
}
|
||||||
}
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// otherwise, check if we have to update. the display only needs to change at midnight!
|
// 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();
|
case PAGE_DISPLAY: {
|
||||||
if (date_time.unit.hour == 0 && date_time.unit.minute == 0 && date_time.unit.second == 0) {
|
watch_date_time date_time = watch_rtc_get_date_time();
|
||||||
_day_one_face_update(*state);
|
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;
|
break;
|
||||||
case EVENT_LIGHT_BUTTON_DOWN:
|
case EVENT_LIGHT_BUTTON_DOWN:
|
||||||
// only illuminate if we're in display mode
|
// 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;
|
break;
|
||||||
case EVENT_LIGHT_BUTTON_UP:
|
case EVENT_LIGHT_BUTTON_UP:
|
||||||
// otherwise use the light button to advance settings pages.
|
// otherwise use the light button to advance settings pages.
|
||||||
if (state->current_page != 0) {
|
switch (state->current_page) {
|
||||||
// go to next setting page...
|
case PAGE_YEAR:
|
||||||
state->current_page = (state->current_page + 1) % 4;
|
// fall through
|
||||||
if (state->current_page == 0) {
|
case PAGE_MONTH:
|
||||||
// ...unless we've been pushed back to display mode.
|
// fall through
|
||||||
movement_request_tick_frequency(1);
|
case PAGE_DAY:
|
||||||
// force display since it normally won't update til midnight.
|
// go to next setting page...
|
||||||
_day_one_face_update(*state);
|
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;
|
break;
|
||||||
case EVENT_ALARM_BUTTON_UP:
|
case EVENT_ALARM_BUTTON_UP:
|
||||||
// if we are on a settings page, increment whatever value we're setting.
|
// if we are on a settings page, increment whatever value we're setting.
|
||||||
if (state->current_page != 0) {
|
switch (state->current_page) {
|
||||||
state->birthday_changed = true;
|
case PAGE_YEAR:
|
||||||
switch (state->current_page) {
|
// fall through
|
||||||
case 1:
|
case PAGE_MONTH:
|
||||||
state->birth_year = state->birth_year + 1;
|
// fall through
|
||||||
if (state->birth_year > state->current_year) state->birth_year = 1900;
|
case PAGE_DAY:
|
||||||
break;
|
_day_one_face_abort_quick_cycle(state);
|
||||||
case 2:
|
_day_one_face_increment(state);
|
||||||
state->birth_month = (state->birth_month % 12) + 1;
|
break;
|
||||||
break;
|
case PAGE_DISPLAY:
|
||||||
case 3:
|
state->current_page = PAGE_DATE;
|
||||||
state->birth_day = state->birth_day + 1;
|
sprintf(buf, "%04d%02d%02d", state->birth_year % 10000, state->birth_month % 100, state->birth_day % 100);
|
||||||
if (state->birth_day == 0 || state->birth_day > days_in_month[state->birth_month - 1]) {
|
watch_display_string(buf, 2);
|
||||||
state->birth_day = 1;
|
state->ticks = 2;
|
||||||
}
|
break;
|
||||||
break;
|
default:
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EVENT_ALARM_LONG_PRESS:
|
case EVENT_ALARM_LONG_PRESS:
|
||||||
// if we aren't already in settings mode, put us there.
|
// if we aren't already in settings mode, put us there.
|
||||||
if (state->current_page == 0) {
|
switch (state->current_page) {
|
||||||
state->current_page++;
|
case PAGE_DISPLAY:
|
||||||
movement_request_tick_frequency(4);
|
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;
|
break;
|
||||||
|
case EVENT_ALARM_LONG_UP:
|
||||||
|
_day_one_face_abort_quick_cycle(state);
|
||||||
|
break;
|
||||||
case EVENT_TIMEOUT:
|
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).
|
// 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);
|
movement_move_to_face(0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -25,18 +25,46 @@
|
|||||||
#ifndef DAY_ONE_FACE_H_
|
#ifndef DAY_ONE_FACE_H_
|
||||||
#define DAY_ONE_FACE_H_
|
#define DAY_ONE_FACE_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DAY ONE face
|
||||||
|
*
|
||||||
|
* This watch face displays the number of days since or until a given date.
|
||||||
|
* It was originally designed to display the number of days you’ve been alive,
|
||||||
|
* but technically it can count up from any date in the 20th century or the
|
||||||
|
* 21st century, so far.
|
||||||
|
*
|
||||||
|
* Long press on the Alarm button to enter customization mode. The text “YR”
|
||||||
|
* will appear, and will allow you to set the year starting from 1959. Press
|
||||||
|
* Alarm repeatedly to advance the year. If your birthday is before 1959,
|
||||||
|
* advance beyond the current year and it will wrap around to 1900.
|
||||||
|
*
|
||||||
|
* Once you have set the year, press Light to set the month (“MO”) and
|
||||||
|
* day (“DA”), advancing the value by pressing Alarm repeatedly.
|
||||||
|
*
|
||||||
|
* Note that at this time, the Day One face does not display the sleep
|
||||||
|
* indicator in sleep mode, which may make the watch appear to be
|
||||||
|
* unresponsive in sleep mode. You can still press the Alarm button to
|
||||||
|
* wake the watch. This UI quirk will be addressed in a future update.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "movement.h"
|
#include "movement.h"
|
||||||
|
|
||||||
// The Day One face is designed to count upwards from the wearer's date of birth. It also functions as an
|
typedef enum {
|
||||||
// interface for setting the birth date register, which other watch faces can use for various purposes.
|
PAGE_DISPLAY,
|
||||||
|
PAGE_YEAR,
|
||||||
|
PAGE_MONTH,
|
||||||
|
PAGE_DAY,
|
||||||
|
PAGE_DATE
|
||||||
|
} day_one_page_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t current_page;
|
day_one_page_t current_page;
|
||||||
uint16_t current_year;
|
|
||||||
uint16_t birth_year;
|
uint16_t birth_year;
|
||||||
uint8_t birth_month;
|
uint8_t birth_month;
|
||||||
uint8_t birth_day;
|
uint8_t birth_day;
|
||||||
bool birthday_changed;
|
bool birthday_changed;
|
||||||
|
bool quick_cycle;
|
||||||
|
uint8_t ticks;
|
||||||
} day_one_state_t;
|
} day_one_state_t;
|
||||||
|
|
||||||
void day_one_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
void day_one_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "discgolf_face.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"
|
#include "watch_utility.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -22,9 +22,12 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
#ifndef DISCGOLF_FACE_H_
|
||||||
|
#define DISCGOLF_FACE_H_
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
* DISC GOLF face
|
||||||
|
*
|
||||||
* Keep track of scores in discgolf or golf!
|
* Keep track of scores in discgolf or golf!
|
||||||
* The watch face operates in three different modes:
|
* The watch face operates in three different modes:
|
||||||
*
|
*
|
||||||
@ -58,10 +61,6 @@
|
|||||||
* lowest score for that course, and saved if it is better.
|
* lowest score for that course, and saved if it is better.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#ifndef DISCGOLF_FACE_H_
|
|
||||||
#define DISCGOLF_FACE_H_
|
|
||||||
|
|
||||||
#include "movement.h"
|
#include "movement.h"
|
||||||
#define courses 11
|
#define courses 11
|
||||||
|
|
||||||
|
@ -26,16 +26,6 @@
|
|||||||
#ifndef DUAL_TIMER_FACE_H_
|
#ifndef DUAL_TIMER_FACE_H_
|
||||||
#define 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
|
* DUAL TIMER
|
||||||
* ==========
|
* ==========
|
||||||
@ -70,8 +60,15 @@
|
|||||||
* the timers. In this case LONG PRESSING MODE will move to the next face instead of moving
|
* the timers. In this case LONG PRESSING MODE will move to the next face instead of moving
|
||||||
* back to the default watch face.
|
* 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 {
|
typedef struct {
|
||||||
uint8_t centiseconds : 7; // 0-59
|
uint8_t centiseconds : 7; // 0-59
|
||||||
uint8_t seconds : 6; // 0-59
|
uint8_t seconds : 6; // 0-59
|
||||||
|
@ -25,9 +25,9 @@
|
|||||||
#ifndef FLASHLIGHT_FACE_H_
|
#ifndef FLASHLIGHT_FACE_H_
|
||||||
#define FLASHLIGHT_FACE_H_
|
#define FLASHLIGHT_FACE_H_
|
||||||
|
|
||||||
#include "movement.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
* FLASHLIGHT face
|
||||||
|
*
|
||||||
* A flashlight for use with the Flashlight sensor board.
|
* A flashlight for use with the Flashlight sensor board.
|
||||||
*
|
*
|
||||||
* When the watch face appears, the display will show "FL" in the top two positions.
|
* When the watch face appears, the display will show "FL" in the top two positions.
|
||||||
@ -35,6 +35,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// Anything you need to keep track of, put it here!
|
// Anything you need to keep track of, put it here!
|
||||||
uint8_t unused;
|
uint8_t unused;
|
||||||
|
@ -25,10 +25,8 @@
|
|||||||
#ifndef GEOMANCY_FACE_H_
|
#ifndef GEOMANCY_FACE_H_
|
||||||
#define 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
|
* 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
|
* 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 {
|
typedef struct {
|
||||||
uint8_t bits : 4;
|
uint8_t bits : 4;
|
||||||
} nibble_t;
|
} nibble_t;
|
||||||
|
@ -25,8 +25,6 @@
|
|||||||
#ifndef HABIT_FACE_H_
|
#ifndef HABIT_FACE_H_
|
||||||
#define HABIT_FACE_H_
|
#define HABIT_FACE_H_
|
||||||
|
|
||||||
#include "movement.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Habit tracking face
|
* Habit tracking face
|
||||||
*
|
*
|
||||||
@ -36,6 +34,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
void habit_face_setup(movement_settings_t *settings, uint8_t watch_face_index,
|
void habit_face_setup(movement_settings_t *settings, uint8_t watch_face_index,
|
||||||
void **context_ptr);
|
void **context_ptr);
|
||||||
void habit_face_activate(movement_settings_t *settings, void *context);
|
void habit_face_activate(movement_settings_t *settings, void *context);
|
||||||
|
@ -22,8 +22,6 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@ -33,57 +31,6 @@
|
|||||||
#include "watch_private_display.h"
|
#include "watch_private_display.h"
|
||||||
#include "watch_buzzer.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 {
|
typedef enum {
|
||||||
interval_setting_0_timer_idx,
|
interval_setting_0_timer_idx,
|
||||||
interval_setting_1_clear_yn,
|
interval_setting_1_clear_yn,
|
||||||
|
@ -22,16 +22,62 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#ifndef INTERVAL_FACE_H_
|
#ifndef INTERVAL_FACE_H_
|
||||||
#define 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)
|
#define INTERVAL_TIMERS 9 // no of available customizable timers (be aware: only 4 bits reserved for this value in struct below)
|
||||||
|
|
||||||
|
@ -25,8 +25,6 @@
|
|||||||
#ifndef INVADERS_FACE_H_
|
#ifndef INVADERS_FACE_H_
|
||||||
#define INVADERS_FACE_H_
|
#define INVADERS_FACE_H_
|
||||||
|
|
||||||
#include "movement.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Remake of the "famous" Casio Number Invaders Game
|
* Remake of the "famous" Casio Number Invaders Game
|
||||||
*
|
*
|
||||||
@ -60,6 +58,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t highscore;
|
uint16_t highscore;
|
||||||
bool sound_on;
|
bool sound_on;
|
||||||
|
480
movement/watch_faces/complication/kitchen_conversions_face.c
Normal file
480
movement/watch_faces/complication/kitchen_conversions_face.c
Normal 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.
|
||||||
|
}
|
87
movement/watch_faces/complication/kitchen_conversions_face.h
Normal file
87
movement/watch_faces/complication/kitchen_conversions_face.h
Normal 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_
|
@ -161,6 +161,10 @@ bool moon_phase_face_loop(movement_event_t event, movement_settings_t *settings,
|
|||||||
state->offset += 86400;
|
state->offset += 86400;
|
||||||
_update(settings, state, state->offset);
|
_update(settings, state, state->offset);
|
||||||
break;
|
break;
|
||||||
|
case EVENT_ALARM_LONG_PRESS:
|
||||||
|
state->offset = 0;
|
||||||
|
_update(settings, state, state->offset);
|
||||||
|
break;
|
||||||
case EVENT_TIMEOUT:
|
case EVENT_TIMEOUT:
|
||||||
// QUESTION: Should timeout reset offset to 0?
|
// QUESTION: Should timeout reset offset to 0?
|
||||||
break;
|
break;
|
||||||
|
@ -25,6 +25,30 @@
|
|||||||
#ifndef MOON_PHASE_FACE_H_
|
#ifndef MOON_PHASE_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -22,89 +22,6 @@
|
|||||||
* SOFTWARE.
|
* 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 <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
@ -25,6 +25,96 @@
|
|||||||
#ifndef MORSECALC_FACE_H_
|
#ifndef MORSECALC_FACE_H_
|
||||||
#define 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 MORSECALC_TOKEN_LEN 32
|
||||||
#define MORSECODE_LEN 5
|
#define MORSECODE_LEN 5
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
* 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
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
@ -25,6 +25,48 @@
|
|||||||
#ifndef ORRERY_FACE_H_
|
#ifndef ORRERY_FACE_H_
|
||||||
#define ORRERY_FACE_H_
|
#define ORRERY_FACE_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ORRERY face
|
||||||
|
*
|
||||||
|
* The Orrery watch face is similar to the Astronomy watch face in that it
|
||||||
|
* calculates properties of the planets, but instead of calculating their
|
||||||
|
* positions in the sky, this watch face calculates their absolute locations
|
||||||
|
* in the solar system. This is only useful if you want to plot the planets
|
||||||
|
* on graph paper, but hey, you never know!
|
||||||
|
*
|
||||||
|
* The controls are identical to the Astronomy watch face: while the title
|
||||||
|
* screen (“Orrery”) is displayed, you can advance through the available
|
||||||
|
* planets with repeated short presses on the Alarm button. The available
|
||||||
|
* planets:
|
||||||
|
*
|
||||||
|
* ME - Mercury
|
||||||
|
* VE - Venus
|
||||||
|
* EA - Earth
|
||||||
|
* LU - Luna, the Earth’s moon
|
||||||
|
* MA - Mars
|
||||||
|
* JU - Jupiter
|
||||||
|
* SA - Saturn
|
||||||
|
* UR - Uranus
|
||||||
|
* NE - Neptune
|
||||||
|
*
|
||||||
|
* Note that the sun is not available in this menu, as the sun is always at
|
||||||
|
* (0,0,0) in this calculation.
|
||||||
|
*
|
||||||
|
* Long press on the Alarm button to calculate the planet’s location, and
|
||||||
|
* after a flashing “C” (for Calculating), you will be presented with the
|
||||||
|
* planet’s X coordinate in astronomical units. Short press Alarm to cycle
|
||||||
|
* through the X, Y and Z coordinates, and then long press Alarm to return
|
||||||
|
* to planet selection.
|
||||||
|
*
|
||||||
|
* The large numbers represent the whole number part, and the two smaller
|
||||||
|
* numbers (in the seconds place) represent the decimal portion. So if you
|
||||||
|
* see “SA X 736” and “SA Y -662”, you can read that as an X coordinate of
|
||||||
|
* 7.36 AU and a Y coordinate of -6.62 AU. You can literally draw a dot at
|
||||||
|
* (0, 0) to represent the sun, and a dot at (7.36, -6.62) to represent
|
||||||
|
* Saturn. (The Z coordinates tend to be pretty close to zero, as the
|
||||||
|
* planets largely orbit on a single plane, the ecliptic.)
|
||||||
|
*/
|
||||||
|
|
||||||
#include "movement.h"
|
#include "movement.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
@ -26,12 +26,11 @@
|
|||||||
#ifndef planetary_hours_face_H_
|
#ifndef planetary_hours_face_H_
|
||||||
#define 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.
|
* 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
|
* 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.
|
* of night time. Obviously the length of these hours varied throughout the year.
|
||||||
@ -74,6 +73,9 @@
|
|||||||
* watch face to work properly!)
|
* watch face to work properly!)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
#include "sunrise_sunset_face.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// Anything you need to keep track of, put it here!
|
// Anything you need to keep track of, put it here!
|
||||||
uint32_t planetary_hours[24];
|
uint32_t planetary_hours[24];
|
||||||
|
@ -26,12 +26,11 @@
|
|||||||
#ifndef planetary_time_face_H_
|
#ifndef planetary_time_face_H_
|
||||||
#define planetary_time_face_H_
|
#define planetary_time_face_H_
|
||||||
|
|
||||||
#include "movement.h"
|
|
||||||
#include "sunrise_sunset_face.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
* PLANETARY TIME face
|
||||||
|
*
|
||||||
* BACKGROUND
|
* BACKGROUND
|
||||||
|
*
|
||||||
* Both the 24 hour day and the order of our weekdays have quite esoteric roots.
|
* 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
|
* 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.
|
* of night time. Obviously the length of these hours varied throughout the year.
|
||||||
@ -77,6 +76,9 @@
|
|||||||
* watch face to work properly!)
|
* watch face to work properly!)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
#include "sunrise_sunset_face.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// Anything you need to keep track of, put it here!
|
// Anything you need to keep track of, put it here!
|
||||||
uint32_t phase_start;
|
uint32_t phase_start;
|
||||||
|
@ -25,6 +25,18 @@
|
|||||||
#ifndef PROBABILITY_FACE_H_
|
#ifndef PROBABILITY_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022 Joey Castillo
|
* Copyright © 2021-2022 Joey Castillo <joeycastillo@utexas.edu> <jose.castillo@gmail.com>
|
||||||
|
* Copyright © 2023 Jeremy O'Brien <neutral@fastmail.com>
|
||||||
|
* Copyright © 2024 Matheus Afonso Martins Moreira <matheus.a.m.moreira@gmail.com> (https://www.matheusmoreira.com/)
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -24,73 +28,162 @@
|
|||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "pulsometer_face.h"
|
#include "pulsometer_face.h"
|
||||||
#include "watch.h"
|
#include "watch.h"
|
||||||
|
|
||||||
#define PULSOMETER_FACE_FREQUENCY_FACTOR (4ul) // refresh rate will be 2 to this power Hz (0 for 1 Hz, 2 for 4 Hz, etc.)
|
#ifndef PULSOMETER_FACE_TITLE
|
||||||
|
#define PULSOMETER_FACE_TITLE "PL"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef PULSOMETER_FACE_CALIBRATION_DEFAULT
|
||||||
|
#define PULSOMETER_FACE_CALIBRATION_DEFAULT (30)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef PULSOMETER_FACE_CALIBRATION_INCREMENT
|
||||||
|
#define PULSOMETER_FACE_CALIBRATION_INCREMENT (10)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// tick frequency will be 2 to this power Hz (0 for 1 Hz, 2 for 4 Hz, etc.)
|
||||||
|
#ifndef PULSOMETER_FACE_FREQUENCY_FACTOR
|
||||||
|
#define PULSOMETER_FACE_FREQUENCY_FACTOR (4ul)
|
||||||
|
#endif
|
||||||
|
|
||||||
#define PULSOMETER_FACE_FREQUENCY (1 << PULSOMETER_FACE_FREQUENCY_FACTOR)
|
#define PULSOMETER_FACE_FREQUENCY (1 << PULSOMETER_FACE_FREQUENCY_FACTOR)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool measuring;
|
||||||
|
int16_t pulses;
|
||||||
|
int16_t ticks;
|
||||||
|
int8_t calibration;
|
||||||
|
} pulsometer_state_t;
|
||||||
|
|
||||||
|
static void pulsometer_display_title(pulsometer_state_t *pulsometer) {
|
||||||
|
watch_display_string(PULSOMETER_FACE_TITLE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pulsometer_display_calibration(pulsometer_state_t *pulsometer) {
|
||||||
|
char buf[3];
|
||||||
|
snprintf(buf, sizeof(buf), "%2hhd", pulsometer->calibration);
|
||||||
|
watch_display_string(buf, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pulsometer_display_measurement(pulsometer_state_t *pulsometer) {
|
||||||
|
char buf[7];
|
||||||
|
snprintf(buf, sizeof(buf), "%-6hd", pulsometer->pulses);
|
||||||
|
watch_display_string(buf, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pulsometer_indicate(pulsometer_state_t *pulsometer) {
|
||||||
|
if (pulsometer->measuring) {
|
||||||
|
watch_set_indicator(WATCH_INDICATOR_LAP);
|
||||||
|
} else {
|
||||||
|
watch_clear_indicator(WATCH_INDICATOR_LAP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pulsometer_start_measurement(pulsometer_state_t *pulsometer) {
|
||||||
|
pulsometer->measuring = true;
|
||||||
|
pulsometer->pulses = INT16_MAX;
|
||||||
|
pulsometer->ticks = 0;
|
||||||
|
|
||||||
|
pulsometer_indicate(pulsometer);
|
||||||
|
|
||||||
|
movement_request_tick_frequency(PULSOMETER_FACE_FREQUENCY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pulsometer_measure(pulsometer_state_t *pulsometer) {
|
||||||
|
if (!pulsometer->measuring) { return; }
|
||||||
|
|
||||||
|
pulsometer->ticks++;
|
||||||
|
|
||||||
|
float ticks_per_minute = 60 << PULSOMETER_FACE_FREQUENCY_FACTOR;
|
||||||
|
float pulses_while_button_held = ticks_per_minute / pulsometer->ticks;
|
||||||
|
float calibrated_pulses = pulses_while_button_held * pulsometer->calibration;
|
||||||
|
calibrated_pulses += 0.5f;
|
||||||
|
|
||||||
|
pulsometer->pulses = (int16_t) calibrated_pulses;
|
||||||
|
|
||||||
|
pulsometer_display_measurement(pulsometer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pulsometer_stop_measurement(pulsometer_state_t *pulsometer) {
|
||||||
|
movement_request_tick_frequency(1);
|
||||||
|
|
||||||
|
pulsometer->measuring = false;
|
||||||
|
|
||||||
|
pulsometer_display_measurement(pulsometer);
|
||||||
|
pulsometer_indicate(pulsometer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pulsometer_cycle_calibration(pulsometer_state_t *pulsometer, int8_t increment) {
|
||||||
|
if (pulsometer->measuring) { return; }
|
||||||
|
|
||||||
|
if (pulsometer->calibration <= 0) {
|
||||||
|
pulsometer->calibration = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t last = pulsometer->calibration;
|
||||||
|
pulsometer->calibration += increment;
|
||||||
|
|
||||||
|
if (pulsometer->calibration > 39) {
|
||||||
|
pulsometer->calibration = last == 39? 1 : 39;
|
||||||
|
}
|
||||||
|
|
||||||
|
pulsometer_display_calibration(pulsometer);
|
||||||
|
}
|
||||||
|
|
||||||
void pulsometer_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
|
void pulsometer_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
|
||||||
(void) settings;
|
(void) settings;
|
||||||
(void) watch_face_index;
|
(void) watch_face_index;
|
||||||
if (*context_ptr == NULL) *context_ptr = malloc(sizeof(pulsometer_state_t));
|
|
||||||
|
if (*context_ptr == NULL) {
|
||||||
|
pulsometer_state_t *pulsometer = malloc(sizeof(pulsometer_state_t));
|
||||||
|
|
||||||
|
pulsometer->calibration = PULSOMETER_FACE_CALIBRATION_DEFAULT;
|
||||||
|
pulsometer->pulses = 0;
|
||||||
|
pulsometer->ticks = 0;
|
||||||
|
|
||||||
|
*context_ptr = pulsometer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void pulsometer_face_activate(movement_settings_t *settings, void *context) {
|
void pulsometer_face_activate(movement_settings_t *settings, void *context) {
|
||||||
(void) settings;
|
(void) settings;
|
||||||
memset(context, 0, sizeof(pulsometer_state_t));
|
|
||||||
|
pulsometer_state_t *pulsometer = context;
|
||||||
|
|
||||||
|
pulsometer->measuring = false;
|
||||||
|
|
||||||
|
pulsometer_display_title(pulsometer);
|
||||||
|
pulsometer_display_calibration(pulsometer);
|
||||||
|
pulsometer_display_measurement(pulsometer);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pulsometer_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
|
bool pulsometer_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
|
||||||
(void) settings;
|
(void) settings;
|
||||||
pulsometer_state_t *pulsometer_state = (pulsometer_state_t *)context;
|
|
||||||
char buf[14];
|
pulsometer_state_t *pulsometer = (pulsometer_state_t *) context;
|
||||||
|
|
||||||
switch (event.event_type) {
|
switch (event.event_type) {
|
||||||
case EVENT_ALARM_BUTTON_DOWN:
|
case EVENT_ALARM_BUTTON_DOWN:
|
||||||
pulsometer_state->measuring = true;
|
pulsometer_start_measurement(pulsometer);
|
||||||
pulsometer_state->pulse = 0xFFFF;
|
|
||||||
pulsometer_state->ticks = 0;
|
|
||||||
movement_request_tick_frequency(PULSOMETER_FACE_FREQUENCY);
|
|
||||||
break;
|
break;
|
||||||
case EVENT_ALARM_BUTTON_UP:
|
case EVENT_ALARM_BUTTON_UP:
|
||||||
case EVENT_ALARM_LONG_UP:
|
case EVENT_ALARM_LONG_UP:
|
||||||
pulsometer_state->measuring = false;
|
pulsometer_stop_measurement(pulsometer);
|
||||||
movement_request_tick_frequency(1);
|
|
||||||
break;
|
break;
|
||||||
case EVENT_TICK:
|
case EVENT_TICK:
|
||||||
if (pulsometer_state->pulse == 0 && !pulsometer_state->measuring) {
|
pulsometer_measure(pulsometer);
|
||||||
switch (pulsometer_state->ticks % 5) {
|
break;
|
||||||
case 0:
|
case EVENT_LIGHT_BUTTON_UP:
|
||||||
watch_display_string(" Hold ", 2);
|
pulsometer_cycle_calibration(pulsometer, 1);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case EVENT_LIGHT_LONG_UP:
|
||||||
watch_display_string(" Alarn", 4);
|
pulsometer_cycle_calibration(pulsometer, PULSOMETER_FACE_CALIBRATION_INCREMENT);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case EVENT_LIGHT_BUTTON_DOWN:
|
||||||
watch_display_string("* Count ", 0);
|
// Inhibit the LED
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
watch_display_string(" 30Beats ", 0);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
watch_clear_display();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pulsometer_state->ticks = (pulsometer_state->ticks + 1) % 5;
|
|
||||||
} else {
|
|
||||||
if (pulsometer_state->measuring && pulsometer_state->ticks) {
|
|
||||||
pulsometer_state->pulse = (int16_t)((30.0 * ((float)(60 << PULSOMETER_FACE_FREQUENCY_FACTOR) / (float)pulsometer_state->ticks)) + 0.5);
|
|
||||||
}
|
|
||||||
if (pulsometer_state->pulse > 240) {
|
|
||||||
watch_display_string(" Hi", 0);
|
|
||||||
} else if (pulsometer_state->pulse < 40) {
|
|
||||||
watch_display_string(" Lo", 0);
|
|
||||||
} else {
|
|
||||||
sprintf(buf, " %-3dbpn", pulsometer_state->pulse);
|
|
||||||
watch_display_string(buf, 0);
|
|
||||||
}
|
|
||||||
if (pulsometer_state->measuring) pulsometer_state->ticks++;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case EVENT_TIMEOUT:
|
case EVENT_TIMEOUT:
|
||||||
movement_move_to_face(0);
|
movement_move_to_face(0);
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022 Joey Castillo
|
* Copyright © 2021-2022 Joey Castillo <joeycastillo@utexas.edu> <jose.castillo@gmail.com>
|
||||||
|
* Copyright © 2022 Alexsander Akers <me@a2.io>
|
||||||
|
* Copyright © 2023 Alex Utter <ooterness@gmail.com>
|
||||||
|
* Copyright © 2024 Matheus Afonso Martins Moreira <matheus.a.m.moreira@gmail.com> (https://www.matheusmoreira.com/)
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -25,13 +30,46 @@
|
|||||||
#ifndef PULSOMETER_FACE_H_
|
#ifndef PULSOMETER_FACE_H_
|
||||||
#define PULSOMETER_FACE_H_
|
#define PULSOMETER_FACE_H_
|
||||||
|
|
||||||
#include "movement.h"
|
/*
|
||||||
|
* PULSOMETER face
|
||||||
|
*
|
||||||
|
* The pulsometer implements a classic mechanical watch complication.
|
||||||
|
* A mechanical pulsometer involves a chronograph with a scale that
|
||||||
|
* allows the user to compute the number of heart beats per minute
|
||||||
|
* in less time. The scale is calibrated, or graduated, for a fixed
|
||||||
|
* number of heart beats, most often 30. The user starts the chronograph
|
||||||
|
* and simultaneously begins counting the heart beats. The movement of
|
||||||
|
* the chronograph's seconds hand over time automatically performs the
|
||||||
|
* computations required. When the calibrated number of heart beats
|
||||||
|
* is reached, the chronograph is stopped and the seconds hand shows
|
||||||
|
* the heart rate.
|
||||||
|
*
|
||||||
|
* The Sensor Watch pulsometer improves this design with user calibration:
|
||||||
|
* it can be graduated to any value between 1 and 39 pulsations per minute.
|
||||||
|
* The default is still 30, mirroring the classic pulsometer calibration.
|
||||||
|
* This feature allows the user to reconfigure the pulsometer to count
|
||||||
|
* many other types of periodic minutely events, making it more versatile.
|
||||||
|
* For example, it can be set to 5 respirations per minute to turn it into
|
||||||
|
* an asthmometer, a nearly identical mechanical watch complication
|
||||||
|
* that doctors might use to quickly measure respiratory rate.
|
||||||
|
*
|
||||||
|
* To use the pulsometer, hold the ALARM button and count the pulses.
|
||||||
|
* When the calibrated number of pulses is reached, release the button.
|
||||||
|
* The display will show the number of pulses per minute.
|
||||||
|
*
|
||||||
|
* In order to measure heart rate, feel for a pulse using the hand with
|
||||||
|
* the watch while holding the button down with the other.
|
||||||
|
* The pulse can be easily felt on the carotid artery of the neck.
|
||||||
|
*
|
||||||
|
* In order to measure breathing rate, simply hold the ALARM button
|
||||||
|
* and count the number of breaths.
|
||||||
|
*
|
||||||
|
* To calibrate the pulsometer, press LIGHT
|
||||||
|
* to cycle to the next integer calibration.
|
||||||
|
* Long press LIGHT to cycle it by 10.
|
||||||
|
*/
|
||||||
|
|
||||||
typedef struct {
|
#include "movement.h"
|
||||||
bool measuring;
|
|
||||||
int16_t pulse;
|
|
||||||
int16_t ticks;
|
|
||||||
} pulsometer_state_t;
|
|
||||||
|
|
||||||
void pulsometer_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
void pulsometer_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||||
void pulsometer_face_activate(movement_settings_t *settings, void *context);
|
void pulsometer_face_activate(movement_settings_t *settings, void *context);
|
||||||
|
@ -357,7 +357,7 @@ static uint32_t _get_true_entropy(void) {
|
|||||||
|
|
||||||
while (!hri_trng_get_INTFLAG_reg(TRNG, TRNG_INTFLAG_DATARDY)); // Wait for TRNG data to be ready
|
while (!hri_trng_get_INTFLAG_reg(TRNG, TRNG_INTFLAG_DATARDY)); // Wait for TRNG data to be ready
|
||||||
|
|
||||||
hri_trng_clear_CTRLA_ENABLE_bit(TRNG);
|
watch_disable_TRNG();
|
||||||
hri_mclk_clear_APBCMASK_TRNG_bit(MCLK);
|
hri_mclk_clear_APBCMASK_TRNG_bit(MCLK);
|
||||||
return hri_trng_read_DATA_reg(TRNG); // Read a single 32-bit word from TRNG and return it
|
return hri_trng_read_DATA_reg(TRNG); // Read a single 32-bit word from TRNG and return it
|
||||||
#endif
|
#endif
|
||||||
|
@ -25,11 +25,8 @@
|
|||||||
#ifndef RANDONAUT_FACE_H_
|
#ifndef RANDONAUT_FACE_H_
|
||||||
#define 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
|
* 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 {
|
typedef struct {
|
||||||
uint8_t mode :3;
|
uint8_t mode :3;
|
||||||
uint8_t location_format :3;
|
uint8_t location_format :3;
|
||||||
|
@ -25,6 +25,16 @@
|
|||||||
#ifndef RATEMETER_FACE_H_
|
#ifndef RATEMETER_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -22,39 +22,6 @@
|
|||||||
* SOFTWARE.
|
* 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 <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
@ -25,6 +25,40 @@
|
|||||||
#ifndef CALCULATOR_FACE_H_
|
#ifndef CALCULATOR_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
#define CALC_MAX_STACK_SIZE 20
|
#define CALC_MAX_STACK_SIZE 20
|
||||||
|
@ -25,6 +25,15 @@
|
|||||||
#ifndef RPN_CALCULATOR_FACE_H_
|
#ifndef RPN_CALCULATOR_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
#define RPN_CALCULATOR_STACK_SIZE 4
|
#define RPN_CALCULATOR_STACK_SIZE 4
|
||||||
|
@ -24,45 +24,12 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "sailing_face.h"
|
#include "sailing_face.h"
|
||||||
#include "watch.h"
|
#include "watch.h"
|
||||||
#include "watch_utility.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 sl_SELECTIONS 6
|
||||||
#define DEFAULT_MINUTES { 5,4,1,0,0,0 }
|
#define DEFAULT_MINUTES { 5,4,1,0,0,0 }
|
||||||
|
|
||||||
|
@ -24,17 +24,43 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#ifndef SAILING_FACE_H_
|
#ifndef SAILING_FACE_H_
|
||||||
#define 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 {
|
typedef enum {
|
||||||
sl_waiting,
|
sl_waiting,
|
||||||
|
@ -25,9 +25,8 @@
|
|||||||
#ifndef SHIPS_BELL_FACE_H_
|
#ifndef SHIPS_BELL_FACE_H_
|
||||||
#define SHIPS_BELL_FACE_H_
|
#define SHIPS_BELL_FACE_H_
|
||||||
|
|
||||||
#include "movement.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
* SHIP'S BELL face
|
||||||
* A ship's bell complication.
|
* A ship's bell complication.
|
||||||
*
|
*
|
||||||
* See: https://en.wikipedia.org/wiki/Ship%27s_bell#Simpler_system
|
* 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)
|
* - long press Alarm button: Cycle through the watches (All/1/2/3)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool bell_enabled;
|
bool bell_enabled;
|
||||||
uint8_t on_watch;
|
uint8_t on_watch;
|
||||||
|
139
movement/watch_faces/complication/simple_coin_flip_face.c
Normal file
139
movement/watch_faces/complication/simple_coin_flip_face.c
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Wesley Aptekar-Cassels
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if __EMSCRIPTEN__
|
||||||
|
#include <time.h>
|
||||||
|
#else
|
||||||
|
#include "saml22j18a.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "simple_coin_flip_face.h"
|
||||||
|
|
||||||
|
#define SIMPLE_COIN_FLIP_REQUIRE_LONG_PRESS_FOR_REFLIP true
|
||||||
|
|
||||||
|
void simple_coin_flip_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
|
||||||
|
(void) settings;
|
||||||
|
(void) watch_face_index;
|
||||||
|
if (*context_ptr == NULL) {
|
||||||
|
*context_ptr = malloc(sizeof(simple_coin_flip_state_t));
|
||||||
|
memset(*context_ptr, 0, sizeof(simple_coin_flip_state_t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void simple_coin_flip_face_activate(movement_settings_t *settings, void *context) {
|
||||||
|
(void) settings;
|
||||||
|
(void) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t get_random(uint32_t max) {
|
||||||
|
#if __EMSCRIPTEN__
|
||||||
|
return rand() % max;
|
||||||
|
#else
|
||||||
|
return arc4random_uniform(max);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void animation_0() {
|
||||||
|
watch_display_string(" ", 8);
|
||||||
|
watch_set_pixel(0, 3);
|
||||||
|
watch_set_pixel(0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void animation_1() {
|
||||||
|
watch_display_string(" ", 8);
|
||||||
|
watch_set_pixel(1, 3);
|
||||||
|
watch_set_pixel(1, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void animation_2() {
|
||||||
|
watch_display_string(" ", 8);
|
||||||
|
watch_set_pixel(2, 2);
|
||||||
|
watch_set_pixel(2, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool simple_coin_flip_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
|
||||||
|
simple_coin_flip_state_t *state = (simple_coin_flip_state_t *)context;
|
||||||
|
|
||||||
|
switch (event.event_type) {
|
||||||
|
case EVENT_ACTIVATE:
|
||||||
|
watch_display_string("flip", 5);
|
||||||
|
state->animation_frame = 0;
|
||||||
|
break;
|
||||||
|
case EVENT_TICK:
|
||||||
|
switch (state->animation_frame) {
|
||||||
|
case 0:
|
||||||
|
case 7:
|
||||||
|
return true;
|
||||||
|
case 1:
|
||||||
|
movement_request_tick_frequency(8);
|
||||||
|
watch_display_string(" ", 4);
|
||||||
|
// fallthrough
|
||||||
|
case 5:
|
||||||
|
animation_0();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
animation_1();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
animation_2();
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
movement_request_tick_frequency(1);
|
||||||
|
if (get_random(2)) {
|
||||||
|
watch_display_string("Heads ", 4);
|
||||||
|
} else {
|
||||||
|
watch_display_string(" Tails", 4);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state->animation_frame++;
|
||||||
|
break;
|
||||||
|
case EVENT_LIGHT_BUTTON_UP:
|
||||||
|
case EVENT_ALARM_BUTTON_UP:
|
||||||
|
if (!SIMPLE_COIN_FLIP_REQUIRE_LONG_PRESS_FOR_REFLIP || state->animation_frame == 0) {
|
||||||
|
state->animation_frame = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EVENT_ALARM_LONG_PRESS:
|
||||||
|
case EVENT_LIGHT_LONG_PRESS:
|
||||||
|
state->animation_frame = 1;
|
||||||
|
break;
|
||||||
|
case EVENT_TIMEOUT:
|
||||||
|
movement_move_to_face(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return movement_default_loop_handler(event, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void simple_coin_flip_face_resign(movement_settings_t *settings, void *context) {
|
||||||
|
(void) settings;
|
||||||
|
(void) context;
|
||||||
|
}
|
||||||
|
|
62
movement/watch_faces/complication/simple_coin_flip_face.h
Normal file
62
movement/watch_faces/complication/simple_coin_flip_face.h
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Wesley Aptekar-Cassels
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SIMPLE_COIN_FLIP_FACE_H_
|
||||||
|
#define SIMPLE_COIN_FLIP_FACE_H_
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A extremely simple coin flip face.
|
||||||
|
*
|
||||||
|
* Press ALARM or LIGHT to flip a coin, after a short animation it will display
|
||||||
|
* "Heads" or "Tails". Long-press to flip again (you can change a #define to
|
||||||
|
* allow a short-press to reflip as well, if you'd like).
|
||||||
|
*
|
||||||
|
* This is for people who want a simpler UI than probability_face or
|
||||||
|
* randonaut_face. While those have more features, this one is more immediately
|
||||||
|
* obvious - useful, for instance, if you are using a coin flip to agree on
|
||||||
|
* something with someone, and want the operation to be clear to someone who
|
||||||
|
* has not had anything explained to them.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t animation_frame;
|
||||||
|
} simple_coin_flip_state_t;
|
||||||
|
|
||||||
|
void simple_coin_flip_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||||
|
void simple_coin_flip_face_activate(movement_settings_t *settings, void *context);
|
||||||
|
bool simple_coin_flip_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
||||||
|
void simple_coin_flip_face_resign(movement_settings_t *settings, void *context);
|
||||||
|
|
||||||
|
#define simple_coin_flip_face ((const watch_face_t){ \
|
||||||
|
simple_coin_flip_face_setup, \
|
||||||
|
simple_coin_flip_face_activate, \
|
||||||
|
simple_coin_flip_face_loop, \
|
||||||
|
simple_coin_flip_face_resign, \
|
||||||
|
NULL, \
|
||||||
|
})
|
||||||
|
|
||||||
|
#endif // SIMPLE_COIN_FLIP_FACE_H_
|
||||||
|
|
236
movement/watch_faces/complication/solstice_face.c
Normal file
236
movement/watch_faces/complication/solstice_face.c
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Wesley Aptekar-Cassels
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include "watch_utility.h"
|
||||||
|
#include "solstice_face.h"
|
||||||
|
|
||||||
|
// Find solstice or equinox time in JDE for a given year, via method from Meeus Ch 27
|
||||||
|
static double calculate_solstice_equinox(uint16_t year, uint8_t k) {
|
||||||
|
double Y = ((double)year - 2000) / 1000;
|
||||||
|
double approx_terms[4][5] = {
|
||||||
|
{2451623.80984, 365242.37404, 0.05169, -0.00411, -0.00057}, // March equinox
|
||||||
|
{2451716.56767, 365241.62603, 0.00325, 0.00888, -0.00030}, // June solstice
|
||||||
|
{2451810.21715, 365242.01767, -0.11575, 0.00337, 0.00078}, // September equinox
|
||||||
|
{2451900.05952, 365242.74049, -0.06223, -0.00823, 0.00032}, // December solstice
|
||||||
|
};
|
||||||
|
double JDE0 = approx_terms[k][0] + Y * (approx_terms[k][1] + Y * (approx_terms[k][2] + Y * (approx_terms[k][3] + Y * approx_terms[k][4])));
|
||||||
|
double T = (JDE0 - 2451545.0) / 36525;
|
||||||
|
double W = 35999.373 * T - 2.47;
|
||||||
|
double dlambda = 1 + (0.0334 * cos(W * M_PI / 180.0)) + (0.0007 * cos(2 * W * M_PI / 180.0));
|
||||||
|
double correction_terms[25][3] = {
|
||||||
|
{485,324.96,1934.136},
|
||||||
|
{203,337.23,32964.467},
|
||||||
|
{199,342.08,20.186},
|
||||||
|
{182,27.85,445267.112},
|
||||||
|
{156,73.14,45036.886},
|
||||||
|
{136,171.52,22518.443},
|
||||||
|
{77,222.54,65928.934},
|
||||||
|
{74,296.72,3034.906},
|
||||||
|
{70,243.58,9037.513},
|
||||||
|
{58,119.81,33718.147},
|
||||||
|
{52,297.17,150.678},
|
||||||
|
{50,21.02,2281.226},
|
||||||
|
{45,247.54,29929.562},
|
||||||
|
{44,325.15,31555.956},
|
||||||
|
{29,60.93,4443.417},
|
||||||
|
{18,155.12,67555.328},
|
||||||
|
{17,288.79,4562.452},
|
||||||
|
{16,198.04,62894.029},
|
||||||
|
{14,199.76,31436.921},
|
||||||
|
{12,95.39,14577.848},
|
||||||
|
{12,287.11,31931.756},
|
||||||
|
{12,320.81,34777.259},
|
||||||
|
{9,227.73,1222.114},
|
||||||
|
{8,15.45,16859.074},
|
||||||
|
};
|
||||||
|
double S = 0;
|
||||||
|
for (int i = 0; i < 25; i++) {
|
||||||
|
S += correction_terms[i][0] * cos((correction_terms[i][1] + correction_terms[i][2] * T) * M_PI / 180.0);
|
||||||
|
}
|
||||||
|
double JDE = JDE0 + (0.00001 * S) / dlambda;
|
||||||
|
|
||||||
|
return JDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert JDE to Gergorian datetime as per Meeus Ch 7
|
||||||
|
static watch_date_time jde_to_date_time(double JDE) {
|
||||||
|
double tmp = JDE + 0.5;
|
||||||
|
double Z = floor(tmp);
|
||||||
|
double F = fmod(tmp, 1);
|
||||||
|
double A;
|
||||||
|
if (Z < 2299161) {
|
||||||
|
A = Z;
|
||||||
|
} else {
|
||||||
|
double alpha = floor((Z - 1867216.25) / 36524.25);
|
||||||
|
A = Z + 1 + alpha - floor(alpha / 4);
|
||||||
|
}
|
||||||
|
double B = A + 1524;
|
||||||
|
double C = floor((B - 122.1) / 365.25);
|
||||||
|
double D = floor(365.25 * C);
|
||||||
|
double E = floor((B - D) / 30.6001);
|
||||||
|
double day = B - D - floor(30.6001 * E) + F;
|
||||||
|
double month;
|
||||||
|
if (E < 14) {
|
||||||
|
month = E - 1;
|
||||||
|
} else {
|
||||||
|
month = E - 13;
|
||||||
|
}
|
||||||
|
double year;
|
||||||
|
if (month > 2) {
|
||||||
|
year = C - 4716;
|
||||||
|
} else {
|
||||||
|
year = C - 4715;
|
||||||
|
}
|
||||||
|
|
||||||
|
double hours = fmod(day, 1) * 24;
|
||||||
|
double minutes = fmod(hours, 1) * 60;
|
||||||
|
double seconds = fmod(minutes, 1) * 60;
|
||||||
|
|
||||||
|
watch_date_time result = {.unit = {
|
||||||
|
floor(seconds),
|
||||||
|
floor(minutes),
|
||||||
|
floor(hours),
|
||||||
|
floor(day),
|
||||||
|
floor(month),
|
||||||
|
floor(year - 2020)
|
||||||
|
}};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void calculate_datetimes(solstice_state_t *state, movement_settings_t *settings) {
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
// TODO: handle DST changes
|
||||||
|
state->datetimes[i] = jde_to_date_time(calculate_solstice_equinox(2020 + state->year, i) + (movement_timezone_offsets[settings->bit.time_zone] / (60.0*24.0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void solstice_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
|
||||||
|
(void) settings;
|
||||||
|
(void) watch_face_index;
|
||||||
|
if (*context_ptr == NULL) {
|
||||||
|
*context_ptr = malloc(sizeof(solstice_state_t));
|
||||||
|
solstice_state_t *state = (solstice_state_t *)*context_ptr;
|
||||||
|
|
||||||
|
watch_date_time now = watch_rtc_get_date_time();
|
||||||
|
state->year = now.unit.year;
|
||||||
|
state->index = 0;
|
||||||
|
calculate_datetimes(state, settings);
|
||||||
|
|
||||||
|
uint32_t now_unix = watch_utility_date_time_to_unix_time(now, 0);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (state->index == 0 && watch_utility_date_time_to_unix_time(state->datetimes[i], 0) > now_unix) {
|
||||||
|
state->index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void solstice_face_activate(movement_settings_t *settings, void *context) {
|
||||||
|
(void) settings;
|
||||||
|
(void) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_main_screen(solstice_state_t *state) {
|
||||||
|
char buf[11];
|
||||||
|
watch_date_time date_time = state->datetimes[state->index];
|
||||||
|
sprintf(buf, " %2d %2d%02d", date_time.unit.year + 20, date_time.unit.month, date_time.unit.day);
|
||||||
|
watch_display_string(buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_date_time(movement_settings_t *settings, solstice_state_t *state) {
|
||||||
|
char buf[11];
|
||||||
|
watch_date_time date_time = state->datetimes[state->index];
|
||||||
|
if (!settings->bit.clock_mode_24h) {
|
||||||
|
if (date_time.unit.hour < 12) {
|
||||||
|
watch_clear_indicator(WATCH_INDICATOR_PM);
|
||||||
|
} else {
|
||||||
|
watch_set_indicator(WATCH_INDICATOR_PM);
|
||||||
|
}
|
||||||
|
date_time.unit.hour %= 12;
|
||||||
|
if (date_time.unit.hour == 0) date_time.unit.hour = 12;
|
||||||
|
}
|
||||||
|
sprintf(buf, "%s%2d%2d%02d%02d", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second);
|
||||||
|
watch_set_colon();
|
||||||
|
watch_display_string(buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool solstice_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
|
||||||
|
solstice_state_t *state = (solstice_state_t *)context;
|
||||||
|
|
||||||
|
switch (event.event_type) {
|
||||||
|
case EVENT_ALARM_LONG_PRESS:
|
||||||
|
show_date_time(settings, state);
|
||||||
|
break;
|
||||||
|
case EVENT_LIGHT_BUTTON_UP:
|
||||||
|
if (state->index == 0) {
|
||||||
|
if (state->year == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state->year--;
|
||||||
|
state->index = 3;
|
||||||
|
calculate_datetimes(state, settings);
|
||||||
|
} else {
|
||||||
|
state->index--;
|
||||||
|
}
|
||||||
|
show_main_screen(state);
|
||||||
|
break;
|
||||||
|
case EVENT_ALARM_BUTTON_UP:
|
||||||
|
state->index++;
|
||||||
|
if (state->index > 3) {
|
||||||
|
if (state->year == 83) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state->year++;
|
||||||
|
state->index = 0;
|
||||||
|
calculate_datetimes(state, settings);
|
||||||
|
}
|
||||||
|
show_main_screen(state);
|
||||||
|
break;
|
||||||
|
case EVENT_ALARM_LONG_UP:
|
||||||
|
watch_clear_colon();
|
||||||
|
watch_clear_indicator(WATCH_INDICATOR_PM);
|
||||||
|
show_main_screen(state);
|
||||||
|
break;
|
||||||
|
case EVENT_ACTIVATE:
|
||||||
|
show_main_screen(state);
|
||||||
|
break;
|
||||||
|
case EVENT_TIMEOUT:
|
||||||
|
movement_move_to_face(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return movement_default_loop_handler(event, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void solstice_face_resign(movement_settings_t *settings, void *context) {
|
||||||
|
(void) settings;
|
||||||
|
(void) context;
|
||||||
|
}
|
||||||
|
|
64
movement/watch_faces/complication/solstice_face.h
Normal file
64
movement/watch_faces/complication/solstice_face.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Wesley Aptekar-Cassels
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SOLSTICE_FACE_H_
|
||||||
|
#define SOLSTICE_FACE_H_
|
||||||
|
|
||||||
|
#include "movement.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A face for telling the dates and times of solstices and equinoxes
|
||||||
|
*
|
||||||
|
* It shows the upcoming solstice or equinox by default. The upper right number
|
||||||
|
* is the year, and the bottom numbers are the date in MMDD format. Use the
|
||||||
|
* alarm / light buttons to go forwards / backwards in time. Long press the
|
||||||
|
* alarm button to show the time of the event, including what weekday it is on,
|
||||||
|
* in your local timezone (DST is not handled).
|
||||||
|
*
|
||||||
|
* Supports the years 2020 - 2083. The calculations are reasonably accurate for
|
||||||
|
* years between 1000 and 3000, but limitations in the sensor watch libraries
|
||||||
|
* (which could easily be worked around) prevent making use of that.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
watch_date_time datetimes[4];
|
||||||
|
uint8_t year;
|
||||||
|
uint8_t index;
|
||||||
|
} solstice_state_t;
|
||||||
|
|
||||||
|
void solstice_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||||
|
void solstice_face_activate(movement_settings_t *settings, void *context);
|
||||||
|
bool solstice_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
||||||
|
void solstice_face_resign(movement_settings_t *settings, void *context);
|
||||||
|
|
||||||
|
#define solstice_face ((const watch_face_t){ \
|
||||||
|
solstice_face_setup, \
|
||||||
|
solstice_face_activate, \
|
||||||
|
solstice_face_loop, \
|
||||||
|
solstice_face_resign, \
|
||||||
|
NULL, \
|
||||||
|
})
|
||||||
|
|
||||||
|
#endif // SOLSTICE_FACE_H_
|
||||||
|
|
@ -25,12 +25,34 @@
|
|||||||
#ifndef STOCK_STOPWATCH_FACE_H_
|
#ifndef STOCK_STOPWATCH_FACE_H_
|
||||||
#define 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.
|
#include "movement.h"
|
||||||
// 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 :-)
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool light_on_button; // determines whether the light button actually triggers the led
|
bool light_on_button; // determines whether the light button actually triggers the led
|
||||||
|
@ -26,6 +26,17 @@
|
|||||||
#ifndef STOPWATCH_FACE_H_
|
#ifndef STOPWATCH_FACE_H_
|
||||||
#define 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"
|
#include "movement.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -197,6 +197,8 @@ static void _sunrise_sunset_face_update_settings_display(movement_event_t event,
|
|||||||
char buf[12];
|
char buf[12];
|
||||||
|
|
||||||
switch (state->page) {
|
switch (state->page) {
|
||||||
|
case 0:
|
||||||
|
return;
|
||||||
case 1:
|
case 1:
|
||||||
sprintf(buf, "LA %c %04d", state->working_latitude.sign ? '-' : '+', abs(_sunrise_sunset_face_latlon_from_struct(state->working_latitude)));
|
sprintf(buf, "LA %c %04d", state->working_latitude.sign ? '-' : '+', abs(_sunrise_sunset_face_latlon_from_struct(state->working_latitude)));
|
||||||
break;
|
break;
|
||||||
|
@ -25,10 +25,18 @@
|
|||||||
#ifndef SUNRISE_SUNSET_FACE_H_
|
#ifndef SUNRISE_SUNSET_FACE_H_
|
||||||
#define 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.
|
#include "movement.h"
|
||||||
// TODO: It also functions as an interface for setting the location register, which other watch faces can use for various purposes.
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t sign: 1; // 0-1
|
uint8_t sign: 1; // 0-1
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user