diff options
Diffstat (limited to 'movement')
| -rw-r--r-- | movement/filesystem.c | 121 | ||||
| -rw-r--r-- | movement/filesystem.h | 9 | ||||
| -rw-r--r-- | movement/make/Makefile | 4 | ||||
| -rw-r--r-- | movement/movement.c | 59 | ||||
| -rw-r--r-- | movement/movement_config.h | 45 | ||||
| -rw-r--r-- | movement/movement_faces.h | 2 | ||||
| -rw-r--r-- | movement/shell.c | 220 | ||||
| -rw-r--r-- | movement/shell.h | 34 | ||||
| -rw-r--r-- | movement/shell_cmd_list.c | 159 | ||||
| -rw-r--r-- | movement/shell_cmd_list.h | 38 | ||||
| -rw-r--r-- | movement/watch_faces/clock/clock_face.c | 291 | ||||
| -rw-r--r-- | movement/watch_faces/clock/clock_face.h (renamed from movement/watch_faces/clock/simple_clock_face.h) | 50 | ||||
| -rw-r--r-- | movement/watch_faces/clock/simple_clock_face.c | 161 | ||||
| -rw-r--r-- | movement/watch_faces/complication/moon_phase_face.c | 4 | ||||
| -rw-r--r-- | movement/watch_faces/complication/pulsometer_face.c | 183 | ||||
| -rw-r--r-- | movement/watch_faces/complication/pulsometer_face.h | 69 | ||||
| -rw-r--r-- | movement/watch_faces/complication/randonaut_face.c | 2 | ||||
| -rw-r--r-- | movement/watch_faces/complication/sunrise_sunset_face.c | 2 | ||||
| -rw-r--r-- | movement/watch_faces/complication/toss_up_face.c | 3 | 
19 files changed, 1100 insertions, 356 deletions
diff --git a/movement/filesystem.c b/movement/filesystem.c index 97e35455..9df0a8d2 100644 --- a/movement/filesystem.c +++ b/movement/filesystem.c @@ -100,7 +100,7 @@ static int filesystem_ls(lfs_t *lfs, const char *path) {          printf("%4ld bytes ", info.size); -        printf("%s\n", info.name); +        printf("%s\r\n", info.name);      }      err = lfs_dir_close(lfs, &dir); @@ -117,11 +117,11 @@ bool filesystem_init(void) {      // reformat if we can't mount the filesystem      // this should only happen on the first boot      if (err < 0) { -        printf("Ignore that error! Formatting filesystem...\n"); +        printf("Ignore that error! Formatting filesystem...\r\n");          err = lfs_format(&lfs, &cfg);          if (err < 0) return false;          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; @@ -139,7 +139,7 @@ bool filesystem_rm(char *filename) {      if (filesystem_file_exists(filename)) {          return lfs_remove(&lfs, filename) == LFS_ERR_OK;      } else { -        printf("rm: %s: No such file\n", filename); +        printf("rm: %s: No such file\r\n", filename);          return false;      }  } @@ -197,13 +197,13 @@ static void filesystem_cat(char *filename) {              char *buf = malloc(info.size + 1);              filesystem_read_file(filename, buf, info.size);              buf[info.size] = '\0'; -            printf("%s\n", buf); +            printf("%s\r\n", buf);              free(buf);          } else { -            printf("\n"); +            printf("\r\n");          }      } 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;  } -void filesystem_process_command(char *line) { -    printf("$ %s", line); -    char *command = strtok(line, " \n"); -     -    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); +int filesystem_cmd_ls(int argc, char *argv[]) { +    if (argc >= 2) { +        filesystem_ls(&lfs, argv[1]);      } 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; +} + diff --git a/movement/filesystem.h b/movement/filesystem.h index 3cd3d092..fa3d9d1a 100644 --- a/movement/filesystem.h +++ b/movement/filesystem.h @@ -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); -/** @brief Handles the interactive file browser when Movement is plugged in to USB. -  * @param line The command that the user typed into the serial console. -  */ -void filesystem_process_command(char *line); +int filesystem_cmd_ls(int argc, char *argv[]); +int filesystem_cmd_cat(int argc, char *argv[]); +int filesystem_cmd_df(int argc, char *argv[]); +int filesystem_cmd_rm(int argc, char *argv[]); +int filesystem_cmd_echo(int argc, char *argv[]);  #endif // FILESYSTEM_H_ diff --git a/movement/make/Makefile b/movement/make/Makefile index 42dfc644..5d53ff7b 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -49,7 +49,9 @@ SRCS += \    ../../littlefs/lfs_util.c \    ../movement.c \    ../filesystem.c \ -  ../watch_faces/clock/simple_clock_face.c \ +  ../shell.c \ +  ../shell_cmd_list.c \ +  ../watch_faces/clock/clock_face.c \    ../watch_faces/clock/world_clock_face.c \    ../watch_faces/clock/beats_face.c \    ../watch_faces/clock/weeknumber_clock_face.c \ diff --git a/movement/movement.c b/movement/movement.c index d780a2f3..816624d4 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -33,6 +33,7 @@  #include "watch.h"  #include "filesystem.h"  #include "movement.h" +#include "shell.h"  #ifndef MOVEMENT_FIRMWARE  #include "movement_config.h" @@ -69,6 +70,31 @@  #define MOVEMENT_DEFAULT_GREEN_COLOR 0xF  #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__  #include <emscripten.h>  #endif @@ -351,11 +377,13 @@ void app_init(void) {      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_green_color = MOVEMENT_DEFAULT_GREEN_COLOR; -    movement_state.settings.bit.button_should_sound = true; -    movement_state.settings.bit.le_interval = 2; -    movement_state.settings.bit.led_duration = 1; +    movement_state.settings.bit.button_should_sound = MOVEMENT_DEFAULT_BUTTON_SOUND; +    movement_state.settings.bit.to_interval = MOVEMENT_DEFAULT_TIMEOUT_INTERVAL; +    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.alarm_ticks = -1;      movement_state.next_available_backup_register = 4; @@ -561,30 +589,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()) { -        char line[256] = {0}; -#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); +        shell_task();      }      event.subsecond = 0; diff --git a/movement/movement_config.h b/movement/movement_config.h index 067ca44b..72203ebd 100644 --- a/movement/movement_config.h +++ b/movement/movement_config.h @@ -28,7 +28,7 @@  #include "movement_faces.h"  const watch_face_t watch_faces[] = { -    simple_clock_face, +    clock_face,      world_clock_face,      sunrise_sunset_face,      moon_phase_face, @@ -52,4 +52,47 @@ const watch_face_t watch_faces[] = {  /* Custom hourly chime tune. Check movement_custom_signal_tunes.h for options. */  #define SIGNAL_TUNE_DEFAULT +/* 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_ diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 7feb0f40..949a071a 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -25,7 +25,7 @@  #ifndef MOVEMENT_FACES_H_  #define MOVEMENT_FACES_H_ -#include "simple_clock_face.h" +#include "clock_face.h"  #include "world_clock_face.h"  #include "preferences_face.h"  #include "set_time_face.h" diff --git a/movement/shell.c b/movement/shell.c new file mode 100644 index 00000000..8f146e59 --- /dev/null +++ b/movement/shell.c @@ -0,0 +1,220 @@ +/* + * 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> + +#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 +} diff --git a/movement/shell.h b/movement/shell.h new file mode 100644 index 00000000..27dbf672 --- /dev/null +++ b/movement/shell.h @@ -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 diff --git a/movement/shell_cmd_list.c b/movement/shell_cmd_list.c new file mode 100644 index 00000000..0ea08a56 --- /dev/null +++ b/movement/shell_cmd_list.c @@ -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; +} + diff --git a/movement/shell_cmd_list.h b/movement/shell_cmd_list.h new file mode 100644 index 00000000..89031a54 --- /dev/null +++ b/movement/shell_cmd_list.h @@ -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 diff --git a/movement/watch_faces/clock/clock_face.c b/movement/watch_faces/clock/clock_face.c new file mode 100644 index 00000000..eab5cd8d --- /dev/null +++ b/movement/watch_faces/clock/clock_face.c @@ -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; +} diff --git a/movement/watch_faces/clock/simple_clock_face.h b/movement/watch_faces/clock/clock_face.h index e74a6e86..c4209e3b 100644 --- a/movement/watch_faces/clock/simple_clock_face.h +++ b/movement/watch_faces/clock/clock_face.h @@ -1,7 +1,13 @@ +/* SPDX-License-Identifier: MIT */ +  /*   * 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 © 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 @@ -22,41 +28,33 @@   * SOFTWARE.   */ -#ifndef SIMPLE_CLOCK_FACE_H_ -#define SIMPLE_CLOCK_FACE_H_ +#ifndef CLOCK_FACE_H_ +#define CLOCK_FACE_H_  /* - * SIMPLE CLOCK FACE + * CLOCK FACE   * - * Displays the current time, matching the original operation of the watch. + * 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" -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; -} simple_clock_state_t; - -void simple_clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); -void simple_clock_face_activate(movement_settings_t *settings, void *context); -bool simple_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context); -void simple_clock_face_resign(movement_settings_t *settings, void *context); -bool simple_clock_face_wants_background_task(movement_settings_t *settings, void *context); +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 simple_clock_face ((const watch_face_t){ \ -    simple_clock_face_setup, \ -    simple_clock_face_activate, \ -    simple_clock_face_loop, \ -    simple_clock_face_resign, \ -    simple_clock_face_wants_background_task, \ +#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 // SIMPLE_CLOCK_FACE_H_ +#endif // CLOCK_FACE_H_ diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c deleted file mode 100644 index fbc2c4b3..00000000 --- a/movement/watch_faces/clock/simple_clock_face.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2022 Joey Castillo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include <stdlib.h> -#include "simple_clock_face.h" -#include "watch.h" -#include "watch_utility.h" -#include "watch_private_display.h" - -static void _update_alarm_indicator(bool settings_alarm_enabled, simple_clock_state_t *state) { -    state->alarm_enabled = settings_alarm_enabled; -    if (state->alarm_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL); -    else watch_clear_indicator(WATCH_INDICATOR_SIGNAL); -} - -void simple_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(simple_clock_state_t)); -        simple_clock_state_t *state = (simple_clock_state_t *)*context_ptr; -        state->signal_enabled = false; -        state->watch_face_index = watch_face_index; -    } -} - -void simple_clock_face_activate(movement_settings_t *settings, void *context) { -    simple_clock_state_t *state = (simple_clock_state_t *)context; - -    if (watch_tick_animation_is_running()) watch_stop_tick_animation(); - -    if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); - -    // handle chime indicator -    if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL); -    else watch_clear_indicator(WATCH_INDICATOR_BELL); - -    // show alarm indicator if there is an active alarm -    _update_alarm_indicator(settings->bit.alarm_enabled, state); - -    watch_set_colon(); - -    // this ensures that none of the timestamp fields will match, so we can re-render them all. -    state->previous_date_time = 0xFFFFFFFF; -} - -bool simple_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { -    simple_clock_state_t *state = (simple_clock_state_t *)context; -    char buf[11]; -    uint8_t pos; - -    watch_date_time date_time; -    uint32_t previous_date_time; -    switch (event.event_type) { -        case EVENT_ACTIVATE: -        case EVENT_TICK: -        case EVENT_LOW_ENERGY_UPDATE: -            date_time = watch_rtc_get_date_time(); -            previous_date_time = state->previous_date_time; -            state->previous_date_time = date_time.reg; - -            // check the battery voltage once a day... -            if (date_time.unit.day != state->last_battery_check) { -                state->last_battery_check = date_time.unit.day; -                watch_enable_adc(); -                uint16_t voltage = watch_get_vcc_voltage(); -                watch_disable_adc(); -                // 2.2 volts will happen when the battery has maybe 5-10% remaining? -                // we can refine this later. -                state->battery_low = (voltage < 2200); -            } - -            // ...and set the LAP indicator if low. -            if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP); - -            if ((date_time.reg >> 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: -            // 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 simple_clock_face_resign(movement_settings_t *settings, void *context) { -    (void) settings; -    (void) context; -} - -bool simple_clock_face_wants_background_task(movement_settings_t *settings, void *context) { -    (void) settings; -    simple_clock_state_t *state = (simple_clock_state_t *)context; -    if (!state->signal_enabled) return false; - -    watch_date_time date_time = watch_rtc_get_date_time(); - -    return date_time.unit.minute == 0; -} diff --git a/movement/watch_faces/complication/moon_phase_face.c b/movement/watch_faces/complication/moon_phase_face.c index 9aac374a..f74de647 100644 --- a/movement/watch_faces/complication/moon_phase_face.c +++ b/movement/watch_faces/complication/moon_phase_face.c @@ -161,6 +161,10 @@ bool moon_phase_face_loop(movement_event_t event, movement_settings_t *settings,              state->offset += 86400;              _update(settings, state, state->offset);              break; +	case EVENT_ALARM_LONG_PRESS: +	    state->offset = 0; +            _update(settings, state, state->offset); +	    break;          case EVENT_TIMEOUT:              // QUESTION: Should timeout reset offset to 0?              break; diff --git a/movement/watch_faces/complication/pulsometer_face.c b/movement/watch_faces/complication/pulsometer_face.c index 2247421c..3c04aa1a 100644 --- a/movement/watch_faces/complication/pulsometer_face.c +++ b/movement/watch_faces/complication/pulsometer_face.c @@ -1,7 +1,11 @@ +/* SPDX-License-Identifier: MIT */ +  /*   * 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   * of this software and associated documentation files (the "Software"), to deal @@ -24,73 +28,162 @@  #include <stdlib.h>  #include <string.h> +  #include "pulsometer_face.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) +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) settings;      (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) 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) {      (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) {          case EVENT_ALARM_BUTTON_DOWN: -            pulsometer_state->measuring = true; -            pulsometer_state->pulse = 0xFFFF; -            pulsometer_state->ticks = 0; -            movement_request_tick_frequency(PULSOMETER_FACE_FREQUENCY); +            pulsometer_start_measurement(pulsometer);              break;          case EVENT_ALARM_BUTTON_UP:          case EVENT_ALARM_LONG_UP: -            pulsometer_state->measuring = false; -            movement_request_tick_frequency(1); +            pulsometer_stop_measurement(pulsometer);              break;          case EVENT_TICK: -            if (pulsometer_state->pulse == 0 && !pulsometer_state->measuring) { -                switch (pulsometer_state->ticks % 5) { -                    case 0: -                        watch_display_string("  Hold  ", 2); -                        break; -                    case 1: -                        watch_display_string(" Alarn", 4); -                        break; -                    case 2: -                        watch_display_string("*   Count ", 0); -                        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++; -            } +            pulsometer_measure(pulsometer); +            break; +        case EVENT_LIGHT_BUTTON_UP: +            pulsometer_cycle_calibration(pulsometer, 1); +            break; +        case EVENT_LIGHT_LONG_UP: +            pulsometer_cycle_calibration(pulsometer, PULSOMETER_FACE_CALIBRATION_INCREMENT); +            break; +        case EVENT_LIGHT_BUTTON_DOWN: +            // Inhibit the LED              break;          case EVENT_TIMEOUT:              movement_move_to_face(0); diff --git a/movement/watch_faces/complication/pulsometer_face.h b/movement/watch_faces/complication/pulsometer_face.h index 288b62c4..5c1dae91 100644 --- a/movement/watch_faces/complication/pulsometer_face.h +++ b/movement/watch_faces/complication/pulsometer_face.h @@ -1,7 +1,12 @@ +/* SPDX-License-Identifier: MIT */ +  /*   * 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   * of this software and associated documentation files (the "Software"), to deal @@ -28,38 +33,44 @@  /*   * PULSOMETER face   * - * The Pulsometer is an implementation of a sort of a classic mechanical - * watch complication. A classic pulsometer complication involves a - * chronograph with a scale calibrated for counting a certain number of - * heartbeats (often 30). You start it and begin counting heartbeats, and - * stop it after counting the specified number of beats. Once stopped, - * the needle will point to your heart rate. - *  - * The pulsometer on Sensor Watch flashes its instructions at launch: - * “Hold Alarm + count 30 beats.” Using the hand on the side where you wear - * your watch, touch your carotid artery (in your neck) and feel for your - * pulse. Once you find it, use your other hand to press and hold the Alarm - * button, and count your heartbeats. When you reach 30 beats, release the - * Alarm button. The display will show a number such as “60 bpm”; this is - * your heart rate in beats per minute. - *  - * Two notes: - *  o For the first few seconds of a measurement, the display will read “Hi”. - *    This indicates that it’s too early for the measured value to be a valid - *    heart rate. Once the measurement is below 240 bpm, the display will update. - *  o If you hold the button down for more than 45 seconds, the display will - *    read “Lo”. If it took this long for you to count 30 heartbeats, this - *    indicates that your heart rate is below 40 beats per minute. + * 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.   */  #include "movement.h" -typedef struct { -    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_activate(movement_settings_t *settings, void *context);  bool pulsometer_face_loop(movement_event_t event, movement_settings_t *settings, void *context); diff --git a/movement/watch_faces/complication/randonaut_face.c b/movement/watch_faces/complication/randonaut_face.c index bca334fb..3bbc147f 100644 --- a/movement/watch_faces/complication/randonaut_face.c +++ b/movement/watch_faces/complication/randonaut_face.c @@ -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 -    hri_trng_clear_CTRLA_ENABLE_bit(TRNG); +    watch_disable_TRNG();      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      #endif diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index 82de9c6e..7330c42c 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -197,6 +197,8 @@ static void _sunrise_sunset_face_update_settings_display(movement_event_t event,      char buf[12];      switch (state->page) { +        case 0: +            return;          case 1:              sprintf(buf, "LA  %c %04d", state->working_latitude.sign ? '-' : '+', abs(_sunrise_sunset_face_latlon_from_struct(state->working_latitude)));              break; diff --git a/movement/watch_faces/complication/toss_up_face.c b/movement/watch_faces/complication/toss_up_face.c index 08dd0052..cf6ca680 100644 --- a/movement/watch_faces/complication/toss_up_face.c +++ b/movement/watch_faces/complication/toss_up_face.c @@ -255,7 +255,8 @@ uint32_t get_true_entropy(void) {      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);      return hri_trng_read_DATA_reg(TRNG); // Read a single 32-bit word from TRNG and return it      #endif  | 
