diff options
| author | joeycastillo <joeycastillo@utexas.edu> | 2023-01-25 09:26:31 -0600 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-01-25 09:26:31 -0600 | 
| commit | 4412b5d08f90084a8e677bea4792851e6d82bdf3 (patch) | |
| tree | c1298f3ea924d8102b33bd796373209dad8be63b /movement/watch_faces | |
| parent | 2b22ecc8e43cc21c56d82ee3f89f32dd7b6b8e4a (diff) | |
| parent | 3303060c59e1591bd2c6bb7cfc5b21e550728d6d (diff) | |
| download | Sensor-Watch-default-handler.tar.gz Sensor-Watch-default-handler.tar.bz2 Sensor-Watch-default-handler.zip  | |
Merge branch 'main' into default-handlerdefault-handler
Diffstat (limited to 'movement/watch_faces')
| -rw-r--r-- | movement/watch_faces/complication/alarm_face.c | 6 | ||||
| -rw-r--r-- | movement/watch_faces/complication/countdown_face.c | 123 | ||||
| -rw-r--r-- | movement/watch_faces/complication/countdown_face.h | 8 | ||||
| -rw-r--r-- | movement/watch_faces/complication/morsecalc_face.c | 391 | ||||
| -rw-r--r-- | movement/watch_faces/complication/morsecalc_face.h | 60 | ||||
| -rw-r--r-- | movement/watch_faces/complication/sailing_face.c | 3 | ||||
| -rw-r--r-- | movement/watch_faces/complication/ships_bell_face.c | 159 | ||||
| -rw-r--r-- | movement/watch_faces/complication/ships_bell_face.h | 67 | ||||
| -rw-r--r-- | movement/watch_faces/complication/tarot_face.c | 50 | 
9 files changed, 812 insertions, 55 deletions
diff --git a/movement/watch_faces/complication/alarm_face.c b/movement/watch_faces/complication/alarm_face.c index 4c0befde..479f2f1e 100644 --- a/movement/watch_faces/complication/alarm_face.c +++ b/movement/watch_faces/complication/alarm_face.c @@ -133,7 +133,7 @@ static void _alarm_face_draw(movement_settings_t *settings, alarm_state_t *state              else {                  if (state->alarm[state->alarm_idx].beeps == 0)                      watch_display_character('o', _blink_idx[alarm_setting_idx_beeps]); -            else +                else                      watch_display_character(state->alarm[state->alarm_idx].beeps + 48, _blink_idx[alarm_setting_idx_beeps]);              }          } @@ -182,7 +182,7 @@ static void _alarm_update_alarm_enabled(movement_settings_t *settings, alarm_sta                  if ((state->alarm[i].day == weekday_idx && alarm_minutes_of_day >= now_minutes_of_day)                      || ((weekday_idx + 1) % 7 == state->alarm[i].day && alarm_minutes_of_day <= now_minutes_of_day)                       || (state->alarm[i].day == ALARM_DAY_WORKDAY && (weekday_idx < 4 -                        || (weekday_idx == 5 && alarm_minutes_of_day >= now_minutes_of_day) +                        || (weekday_idx == 4 && alarm_minutes_of_day >= now_minutes_of_day)                          || (weekday_idx == 6 && alarm_minutes_of_day <= now_minutes_of_day)))                      || (state->alarm[i].day == ALARM_DAY_WEEKEND && (weekday_idx == 5                          || (weekday_idx == 6 && alarm_minutes_of_day >= now_minutes_of_day) @@ -446,7 +446,7 @@ bool alarm_face_loop(movement_event_t event, movement_settings_t *settings, void          break;      default:          movement_default_loop_handler(event, settings); -      break; +        break;      }      return true; diff --git a/movement/watch_faces/complication/countdown_face.c b/movement/watch_faces/complication/countdown_face.c index f3bbd963..15fc375d 100644 --- a/movement/watch_faces/complication/countdown_face.c +++ b/movement/watch_faces/complication/countdown_face.c @@ -1,6 +1,7 @@  /*   * MIT License   * + * Copyright (c) 2023 Konrad Rieck   * Copyright (c) 2022 Wesley Ellis   *   * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -30,15 +31,58 @@  #include "watch.h"  #include "watch_utility.h" +/* +    Slight extension of the original countdown face by Wesley Ellis. + +    - Press the light button to enter setting mode and adjust the +      countdown timer. + +    - Start and pause the countdown using the alarm button, similar to the +      stopwatch face. + +    - When paused or terminated, press the light button to restore the +      last entered countdown. +*/  #define CD_SELECTIONS 3  #define DEFAULT_MINUTES 3 +static bool quick_ticks_running; + +static void abort_quick_ticks(countdown_state_t *state) { +    if (quick_ticks_running) { +        quick_ticks_running = false; +        if (state->mode == cd_setting) +            movement_request_tick_frequency(4); +        else +            movement_request_tick_frequency(1); +    } +}  static inline int32_t get_tz_offset(movement_settings_t *settings) {      return movement_timezone_offsets[settings->bit.time_zone] * 60;  } +static inline void store_countdown(countdown_state_t *state) { +    /* Store set countdown time */ +    state->set_hours = state->hours; +    state->set_minutes = state->minutes; +    state->set_seconds = state->seconds; +} + +static inline void load_countdown(countdown_state_t *state) { +    /* Load set countdown time */ +    state->hours = state->set_hours; +    state->minutes = state->set_minutes; +    state->seconds = state->set_seconds; +} + +static inline void button_beep(movement_settings_t *settings) { +    // play a beep as confirmation for a button press (if applicable) +    if (settings->bit.button_should_sound) +        watch_buzzer_play_note(BUZZER_NOTE_C7, 50); +} +  static void start(countdown_state_t *state, movement_settings_t *settings) {      watch_date_time now = watch_rtc_get_date_time(); @@ -55,25 +99,24 @@ static void draw(countdown_state_t *state, uint8_t subsecond) {      uint32_t delta;      div_t result; -    uint8_t hour, min, sec;      switch (state->mode) {          case cd_running:              delta = state->target_ts - state->now_ts;              result = div(delta, 60); -            sec = result.rem; +            state->seconds = result.rem;              result = div(result.quot, 60); -            hour = result.quot; -            min = result.rem; - -            sprintf(buf, "CD  %2d%02d%02d", hour, min, sec); +            state->hours = result.quot; +            state->minutes = result.rem; +            sprintf(buf, "CD  %2d%02d%02d", state->hours, state->minutes, state->seconds);              break; -        case cd_waiting: +        case cd_reset: +        case cd_paused:              sprintf(buf, "CD  %2d%02d%02d", state->hours, state->minutes, state->seconds);              break;          case cd_setting:              sprintf(buf, "CD  %2d%02d%02d", state->hours, state->minutes, state->seconds); -            if (subsecond % 2) { +            if (!quick_ticks_running && subsecond % 2) {                  switch(state->selection) {                      case 0:                          buf[4] = buf[5] = ' '; @@ -93,10 +136,17 @@ static void draw(countdown_state_t *state, uint8_t subsecond) {      watch_display_string(buf, 0);  } +static void pause(countdown_state_t *state) { +    state->mode = cd_paused; +    movement_cancel_background_task(); +    watch_clear_indicator(WATCH_INDICATOR_BELL); +} +  static void reset(countdown_state_t *state) { -    state->mode = cd_waiting; +    state->mode = cd_reset;      movement_cancel_background_task();      watch_clear_indicator(WATCH_INDICATOR_BELL); +    load_countdown(state);  }  static void ring(countdown_state_t *state) { @@ -131,6 +181,8 @@ void countdown_face_setup(movement_settings_t *settings, uint8_t watch_face_inde          countdown_state_t *state = (countdown_state_t *)*context_ptr;          memset(*context_ptr, 0, sizeof(countdown_state_t));          state->minutes = DEFAULT_MINUTES; +        state->mode = cd_reset; +        store_countdown(state);      }  } @@ -143,8 +195,10 @@ void countdown_face_activate(movement_settings_t *settings, void *context) {          watch_set_indicator(WATCH_INDICATOR_BELL);      }      watch_set_colon(); -} +    movement_request_tick_frequency(1); +    quick_ticks_running = false; +}  bool countdown_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {      (void) settings; @@ -155,26 +209,44 @@ bool countdown_face_loop(movement_event_t event, movement_settings_t *settings,              draw(state, event.subsecond);              break;          case EVENT_TICK: +            if (quick_ticks_running) { +                if (watch_get_pin_level(BTN_ALARM)) +                    settings_increment(state); +                else +                    abort_quick_ticks(state); +            } +              if (state->mode == cd_running) {                  state->now_ts++;              }              draw(state, event.subsecond);              break; +        case EVENT_MODE_BUTTON_UP: +            abort_quick_ticks(state); +            movement_move_to_next_face(); +            break;          case EVENT_LIGHT_BUTTON_UP:              switch(state->mode) {                  case cd_running:                      movement_illuminate_led();                      break; -                case cd_waiting: +                case cd_paused: +                    reset(state); +                    button_beep(settings); +                    break; +                case cd_reset:                      state->mode = cd_setting;                      movement_request_tick_frequency(4); +                    button_beep(settings);                      break;                  case cd_setting:                      state->selection++;                      if(state->selection >= CD_SELECTIONS) {                          state->selection = 0; -                        state->mode = cd_waiting; +                        state->mode = cd_reset; +                        store_countdown(state);                          movement_request_tick_frequency(1); +                        button_beep(settings);                      }                      break;              } @@ -183,12 +255,15 @@ bool countdown_face_loop(movement_event_t event, movement_settings_t *settings,          case EVENT_ALARM_BUTTON_UP:              switch(state->mode) {                  case cd_running: -                    reset(state); +                    pause(state); +                    button_beep(settings);                      break; -                case cd_waiting: +                case cd_reset: +                case cd_paused:                      if (!(state->hours == 0 && state->minutes == 0 && state->seconds == 0)) {                          // Only start the timer if we have a valid time.                          start(state, settings); +                        button_beep(settings);                      }                      break;                  case cd_setting: @@ -197,19 +272,20 @@ bool countdown_face_loop(movement_event_t event, movement_settings_t *settings,              }              draw(state, event.subsecond);              break; -        case EVENT_BACKGROUND_TASK: -            ring(state); -            break;          case EVENT_ALARM_LONG_PRESS:              if (state->mode == cd_setting) { -                    state->hours = 0; -                    state->minutes = 0; -                    state->seconds = 0; -                    draw(state, event.subsecond); -                    break; +                quick_ticks_running = true; +                movement_request_tick_frequency(8);              }              break; +        case EVENT_ALARM_LONG_UP: +            abort_quick_ticks(state); +            break; +        case EVENT_BACKGROUND_TASK: +            ring(state); +            break;          case EVENT_TIMEOUT: +            abort_quick_ticks(state);              movement_move_to_face(0);              break;          case EVENT_LOW_ENERGY_UPDATE: @@ -227,6 +303,7 @@ void countdown_face_resign(movement_settings_t *settings, void *context) {      countdown_state_t *state = (countdown_state_t *)context;      if (state->mode == cd_setting) {          state->selection = 0; -        state->mode = cd_waiting; +        state->mode = cd_reset; +        store_countdown(state);      }  } diff --git a/movement/watch_faces/complication/countdown_face.h b/movement/watch_faces/complication/countdown_face.h index f6c845d6..12bb1d1e 100644 --- a/movement/watch_faces/complication/countdown_face.h +++ b/movement/watch_faces/complication/countdown_face.h @@ -40,9 +40,10 @@ movement_schedule_background_task() while the timer is running.  typedef enum { -    cd_waiting, +    cd_paused,      cd_running, -    cd_setting +    cd_setting, +    cd_reset  } countdown_mode_t;  typedef struct { @@ -51,6 +52,9 @@ typedef struct {      uint8_t hours;      uint8_t minutes;      uint8_t seconds; +    uint8_t set_hours; +    uint8_t set_minutes; +    uint8_t set_seconds;      uint8_t selection;      countdown_mode_t mode;  } countdown_state_t; diff --git a/movement/watch_faces/complication/morsecalc_face.c b/movement/watch_faces/complication/morsecalc_face.c new file mode 100644 index 00000000..c54c0ead --- /dev/null +++ b/movement/watch_faces/complication/morsecalc_face.c @@ -0,0 +1,391 @@ +/* + * MIT License + * + * Copyright (c) 2023 Christian Chapman + * + * 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. + */ + +/* +## 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` to quit + - long-press `alarm` to show stack + - long-press `light` to toggle the light +    +## Morse code token entry +As you enter `.`s and `-`s, the morse code char you've entered will appear in the top center digit. +At the top right is the # of morse code `.`/`-` you've input so far. The character resets at the 6th `.`/`-`. +Once you have the character you want to enter, push `mode` to enter it.  +The character will be appended to the current token, whose 6 trailing chars are shown on the main display. + +Once you've typed in the token you want, enter a blank Morse code character and then push `mode`. +This submits it to the calculator. +    +Special characters: + + - Backspace is `(` (`-.--.`).  + - Clear token input without submitting to calculator is `Start transmission` (`-.-.-`). +     +## Writing commands +First the calculator will try to interpret the token as a command/stack operation.  +Commands are defined in `calc_dict[]` in `movement/lib/morsecalc/calc_fns.h`. +If the command doesn't appear in the dictionary, the calculator tries to interpret the token as a number. +  +## Writing numbers +Numbers are written like floating point strings.  +Entering a number pushes it to the top of the stack if there's room. +This can get long, so for convenience numerals can also be written in binary with .- = 01. + +    0   1    2    3    4    5    6    7    8    9 +    .   -    -.   --   -..  -.-  --.  ---  -... -..- +    e   t    n    m    d    k    g    o    b    x + + - Exponent signs must be entered as "p". + - Decimal place "." can be entered as "h" (code ....) + - Sign "-" can be entered as "Ch digraph" (code ----) +  +For example: "4.2e-3" can be entered directly, or as "4h2pC3" +  similarly, "0.0042" can also be entered as "eheedn" + +Once you submit a number to the watch face, it pushes it to the top of the stack if there's room. +         +## Number display +After a command runs, the top of the stack is displayed in this format: +    +  - Main 4 digits = leading 4 digits +  - Last 2 digits = exponent +  - Top middle = [Stack location, Sign of number] +  - Top right = [Stack exponent, Sign of exponent] +   +Blank sign digit means positive. +So for example, the watch face might look like this: + +    [   0 -5] +    [4200 03] + +... representing `+4.200e-3` is in stack location 0 (the top) and it's one of five items in the stack. + +## Looking at the stack + +To show the top of the stack, push and hold `light`/`alarm` or submit a blank token by pushing `mode` a bunch of times. +To show the N-th stack item (0 through 9): + + - Put in the Morse code for N without pushing the mode button. + - Push and hold `alarm`. +     +To show the memory register, use `m` instead of a number.  +    +To see all the calculator operations and their token aliases, see the `calc_dict[]` struct in `calc_fns.h`  +*/ + +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "morsecalc_face.h" +#include "watch.h" +#include "watch_utility.h" +#include "watch_private_display.h" + +// Display float on screen +void morsecalc_print_float(double d) {  +    // Special cases  +    if(d == 0) { +        watch_display_string("     0", 4);  +        return; +    } +    else if(isnan(d)) { +        watch_display_string("   nan", 4); +        return; +    } +    else if(d == (1.0)/(0.0)) { +        watch_display_string("   inf", 4); +        return; +    } +    else if(d == (-1.0)/(0.0)) { +        watch_display_character('X', 1); +        watch_display_string("   inf", 4); +        return; +    } + +    // Record number properties +    // Sign +    int is_negative = d<0; +    if(is_negative) d = -d;  + +    // Order of magnitude +    int om = (int) floor(log(d)/log(10)); +    int om_is_negative = (om<0); + +    // Get the first 4 significant figures +    int digits; +    digits = round(d*pow(10.0, 3-om)); +    if(digits>9999) { +        digits = 1000; +        om++; +    } + +    // Print signs +    if(is_negative) { +		// Xi; see https://joeycastillo.github.io/Sensor-Watch-Documentation/segmap +		watch_set_pixel(0,11); +		watch_set_pixel(2,12); +		watch_set_pixel(2,11);  +	} +    else watch_display_character(' ', 1);  +    if(om_is_negative) watch_set_pixel(1,9);  +    else watch_display_character(' ', 2);  + +    // Print first 4 significant figures +    watch_display_character('0'+(digits/1000)%10, 4); +    watch_display_character('0'+(digits/100 )%10, 5); +    watch_display_character('0'+(digits/10  )%10, 6); +    watch_display_character('0'+(digits/1   )%10, 7); + +    // Prinat exponent +    if(om_is_negative) om = -om; // Make exponent positive for display +    if(om<=99) { +        watch_display_character('0'+(om/10  )%10, 8); +        watch_display_character('0'+(om/1   )%10, 9); +    } else { // Over/underflow +        if(om_is_negative) watch_display_string("    uf", 4); +        else watch_display_string("    of", 4); +        if(om<9999) { // Use main display to show order of magnitude +            // (Should always succeed; max double is <2e308) +            watch_display_character('0'+(om/1000)%10, 4); +            watch_display_character('0'+(om/100 )%10, 5); +            watch_display_character('0'+(om/10  )%10, 6); +            watch_display_character('0'+(om/1   )%10, 7); +        } +    } +    return; +} + +// Print current input token +void morsecalc_print_token(morsecalc_state_t *mcs) { +    watch_display_string("          ", 0); // Clear display + +    // Print morse code buffer +    char c = mc_dec(mcs->mc->b); // Decode the morse code buffer's current contents +    if('\0' == c) c = ' '; // Needed for watch_display_character +    watch_display_character(c, 0); // Display current morse code char in mode position +    watch_display_character('0'+(mcs->mc->bidx), 3); // Display buffer position in top right + +    // Print last 6 chars of current input line  +    uint8_t nlen = strlen(mcs->token); // number of characters in token +    uint8_t nprint = min(nlen,6); // number of characters to print +    watch_display_string(mcs->token+nlen-nprint, 10-nprint); // print right-aligned +    return; +} + +// Clear token buffer +void morsecalc_reset_token(morsecalc_state_t *mcs) {  +    memset(mcs->token, '\0', MORSECALC_TOKEN_LEN*sizeof(mcs->token[0])); +    mcs->idxt = 0; +    return; +} + +// Print stack or memory register contents.  +void morsecalc_print_stack(morsecalc_state_t * mcs) { +    watch_display_string("          ", 0); // Clear display + +    char c = mc_dec(mcs->mc->b);  +    if('m' == c) { // Display memory  +        morsecalc_print_float(mcs->cs->mem); +        watch_display_character(c, 0); +    }  +    else { +        // If the morse code buffer has a numeral in it, print that stack item  +        // Otherwise print top of stack +        uint8_t idx = 0; +        if(c >= '0' && c <= '9') idx = c - '0'; +        if(idx >= mcs->cs->s) watch_display_string(" empty", 4); // Stack empty +        else morsecalc_print_float(mcs->cs->stack[mcs->cs->s-1-idx]); // Print stack item + +        watch_display_character('0'+idx, 0); // Print which stack item this is top center +    } +    watch_display_character('0'+(mcs->cs->s), 3); // Print the # of stack items top right  +    return; +} + +// Write something into the morse code buffer. +// Input: c = dot (0), dash (1), or 'complete' ('x') +void morsecalc_input(morsecalc_state_t * mcs, char c) { +    int status = 0; +    if( c != 'x' ) { // Dot or dash received +        mc_input(mcs->mc, c); +        morsecalc_print_token(mcs); +    }  +    else { // Morse code character finished +        char dec = mc_dec(mcs->mc->b);  +        mc_reset(mcs->mc); +        switch(dec) { +            case '\0': // Invalid character, do nothing +                morsecalc_print_token(mcs); +                break; +                 +            case ' ': // Submit token to calculator +                if(strlen(mcs->token) > 0) { +                    status = calc_input(mcs->cs, mcs->token); +                    morsecalc_reset_token(mcs);  +                }  +                morsecalc_print_stack(mcs);    +                break; +                 +            case '(': // -.--. Erase previous character in token +                if(mcs->idxt>0) { +                    mcs->idxt--; +                    mcs->token[mcs->idxt] = '\0'; +                } +                morsecalc_print_token(mcs); +                break; +                 +            case 'S': // -.-.- Erase entire token without submitting +                morsecalc_reset_token(mcs);  +                morsecalc_print_stack(mcs);    +                break; +                 +            default: // Add character to token +                if(mcs->idxt < MORSECALC_TOKEN_LEN-1) { +                    mcs->token[mcs->idxt] = dec; +                    mcs->idxt++;  +                    morsecalc_print_token(mcs); +                } +                else  watch_display_string("  full", 4);  +                break; +        } +    } +     +    // Print errors if there are any +    switch(status) { +        case  0: break; // Success +        case -1: watch_display_string("cmderr", 4); break; // Unrecognized command +        case -2: watch_display_string("stkerr", 4); break; // Bad stack size +        default: watch_display_string("   err", 4); break; // Other error  +    } +     +    return;     +} + +void morsecalc_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(morsecalc_state_t));  +        morsecalc_state_t *mcs = (morsecalc_state_t *)*context_ptr; +        morsecalc_reset_token(mcs);  +         +        mcs->cs = (calc_state_t *) malloc(sizeof(calc_state_t)); +        calc_init(mcs->cs);  +         +        mcs->mc = (mc_state_t *) malloc(sizeof(mc_state_t)); +        mc_reset(mcs->mc); + +        mcs->led_is_on = 0; +    } +    return; +} + +void morsecalc_face_activate(movement_settings_t *settings, void *context) { +    (void) settings; +    morsecalc_state_t *mcs = (morsecalc_state_t *) context; +    mc_reset(mcs->mc); +    morsecalc_print_stack(mcs); +    return; +} + +bool morsecalc_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { +    morsecalc_state_t *mcs = (morsecalc_state_t *) context; +    switch(event.event_type) { +    // input +    case EVENT_ALARM_BUTTON_UP: +    // dot +        morsecalc_input(mcs, '.');     +        break; +    case EVENT_LIGHT_BUTTON_UP: +    // dash +        morsecalc_input(mcs, '-');  +        break; +    case EVENT_MODE_BUTTON_UP: +    // submit character +        morsecalc_input(mcs, 'x');  +        break; + +    // show stack +    case EVENT_ALARM_LONG_PRESS: +        morsecalc_print_stack(mcs); +		mc_reset(mcs->mc); +        break; + +    // toggle light +    case EVENT_LIGHT_LONG_PRESS: +        mcs->led_is_on = !mcs->led_is_on; +        if(mcs->led_is_on) { +			watch_set_led_color(settings->bit.led_red_color ? (0xF | settings->bit.led_red_color << 4) : 0, +								settings->bit.led_green_color ? (0xF | settings->bit.led_green_color << 4) : 0); +            movement_request_tick_frequency(4); +        } +        else { +            watch_set_led_off(); +            movement_request_tick_frequency(1); +        } +        break; + +    // quit +    case EVENT_TIMEOUT: +        movement_move_to_next_face(); +        break; +    case EVENT_MODE_LONG_PRESS: +        movement_move_to_next_face(); +        break; + +    case EVENT_TICK: +        if(mcs->led_is_on) {  +			watch_set_led_color(settings->bit.led_red_color ? (0xF | settings->bit.led_red_color << 4) : 0, +				settings->bit.led_green_color ? (0xF | settings->bit.led_green_color << 4) : 0); +		} +        break; +	} +     +    return true; +} + +void morsecalc_face_resign(movement_settings_t *settings, void *context) { +    (void) settings; +    morsecalc_state_t *mcs = (morsecalc_state_t *) context; +    mcs->led_is_on = 0; +	watch_set_led_off(); +    return; +} + diff --git a/movement/watch_faces/complication/morsecalc_face.h b/movement/watch_faces/complication/morsecalc_face.h new file mode 100644 index 00000000..bd0fd416 --- /dev/null +++ b/movement/watch_faces/complication/morsecalc_face.h @@ -0,0 +1,60 @@ +/* + * MIT License + * + * Copyright (c) 2023 Christian Chapman + * + * 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 MORSECALC_FACE_H_ +#define MORSECALC_FACE_H_ +#define MORSECALC_TOKEN_LEN 9 + +#include "movement.h" +#include "calc.h" +#include "mc.h" + +void morsecalc_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void morsecalc_face_activate(movement_settings_t *settings, void *context); +bool morsecalc_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void morsecalc_face_resign(movement_settings_t *settings, void *context); + +typedef struct { +	calc_state_t *cs; +	mc_state_t *mc;  +	char token[MORSECALC_TOKEN_LEN]; +	uint8_t idxt; +	uint8_t led_is_on; +} morsecalc_state_t; + +void morsecalc_print_float(double d); +void morsecalc_print_token(morsecalc_state_t *mcs); +void morsecalc_print_stack(morsecalc_state_t *mcs); +void morsecalc_reset_token(morsecalc_state_t *mcs); +void morsecalc_input(morsecalc_state_t *mcs, char c); + +#define morsecalc_face ((const watch_face_t){ \ +    morsecalc_face_setup, \ +    morsecalc_face_activate, \ +    morsecalc_face_loop, \ +    morsecalc_face_resign, \ +    NULL, \ +}) + +#endif // MORSECALC_FACE_H_ diff --git a/movement/watch_faces/complication/sailing_face.c b/movement/watch_faces/complication/sailing_face.c index cec04224..81d99db1 100644 --- a/movement/watch_faces/complication/sailing_face.c +++ b/movement/watch_faces/complication/sailing_face.c @@ -33,7 +33,6 @@  #define sl_SELECTIONS 6  #define DEFAULT_MINUTES { 5,4,1,0,0,0 } -#define UNUSED(x) (void)(x)  static inline int32_t get_tz_offset(movement_settings_t *settings) {      return movement_timezone_offsets[settings->bit.time_zone] * 60; @@ -66,7 +65,7 @@ static void start(sailing_state_t *state, movement_settings_t *settings) {  }  static void draw(sailing_state_t *state, uint8_t subsecond, movement_settings_t *settings) { -    UNUSED(settings); +    (void) settings;      char tmp[24];      char buf[16]; diff --git a/movement/watch_faces/complication/ships_bell_face.c b/movement/watch_faces/complication/ships_bell_face.c new file mode 100644 index 00000000..fbcfea8f --- /dev/null +++ b/movement/watch_faces/complication/ships_bell_face.c @@ -0,0 +1,159 @@ +/* + * MIT License + * + * Copyright (c) 2023 buckket + * + * 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 <stdio.h> +#include <string.h> +#include "ships_bell_face.h" + +static void ships_bell_ring() { +    watch_date_time date_time = watch_rtc_get_date_time(); + +    date_time.unit.hour %= 4; +    date_time.unit.hour = date_time.unit.hour == 0 && date_time.unit.minute < 30 ? 4 : date_time.unit.hour; + +    for (uint8_t i = 0; i < date_time.unit.hour; i++) { +        watch_buzzer_play_note(BUZZER_NOTE_C8, 75); +        watch_buzzer_play_note(BUZZER_NOTE_REST, 75); +        watch_buzzer_play_note(BUZZER_NOTE_C8, 100); +        watch_buzzer_play_note(BUZZER_NOTE_REST, 250); +    } + +    if (date_time.unit.minute >= 30 ? 1 : 0) { +        watch_buzzer_play_note(BUZZER_NOTE_C8, 100); +    } +} + +static void ships_bell_draw(ships_bell_state_t *state) { +    char buf[8]; + +    if (state->on_watch) { +        sprintf(buf, "%d", state->on_watch); +    } else { +        sprintf(buf, " "); +    } + +    watch_date_time date_time = watch_rtc_get_date_time(); +    date_time.unit.hour %= 4; + +    sprintf(buf + 1, " %d%02d%02d", date_time.unit.hour, date_time.unit.minute, date_time.unit.second); +    watch_display_string(buf, 3); +} + +void ships_bell_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(ships_bell_state_t)); +        memset(*context_ptr, 0, sizeof(ships_bell_state_t)); +    } +} + +void ships_bell_face_activate(movement_settings_t *settings, void *context) { +    (void) settings; + +    ships_bell_state_t *state = (ships_bell_state_t *) context; +    if (state->bell_enabled) watch_set_indicator(WATCH_INDICATOR_BELL); +    else watch_clear_indicator(WATCH_INDICATOR_BELL); + +    watch_display_string("SB", 0); +    watch_set_colon(); +} + +bool ships_bell_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { +    (void) settings; + +    ships_bell_state_t *state = (ships_bell_state_t *) context; + +    switch (event.event_type) { +        case EVENT_ACTIVATE: +        case EVENT_TICK: +            ships_bell_draw(state); +            break; +        case EVENT_MODE_BUTTON_UP: +            movement_move_to_next_face(); +            break; +        case EVENT_LIGHT_BUTTON_UP: +            movement_illuminate_led(); +            break; +        case EVENT_ALARM_BUTTON_UP: +            state->bell_enabled = !state->bell_enabled; +            if (state->bell_enabled) watch_set_indicator(WATCH_INDICATOR_BELL); +            else watch_clear_indicator(WATCH_INDICATOR_BELL); +            break; +        case EVENT_ALARM_LONG_PRESS: +            state->on_watch = (state->on_watch + 1) % 4; +            ships_bell_draw(state); +            break; +        case EVENT_TIMEOUT: +            movement_move_to_face(0); +            break; +        case EVENT_LOW_ENERGY_UPDATE: +            break; +        case EVENT_BACKGROUND_TASK: +            if (watch_is_buzzer_or_led_enabled()) { +                ships_bell_ring(); +            } else { +                watch_enable_buzzer(); +                ships_bell_ring(); +                watch_disable_buzzer(); +            } +            break; +        default: +            break; +    } + +    return true; +} + +void ships_bell_face_resign(movement_settings_t *settings, void *context) { +    (void) settings; +    (void) context; +} + +bool ships_bell_face_wants_background_task(movement_settings_t *settings, void *context) { +    (void) settings; + +    ships_bell_state_t *state = (ships_bell_state_t *) context; +    if (!state->bell_enabled) return false; + +    watch_date_time date_time = watch_rtc_get_date_time(); +    if (!(date_time.unit.minute == 0 || date_time.unit.minute == 30)) return false; + +    date_time.unit.hour %= 12; +    switch (state->on_watch) { +        case 1: +            return (date_time.unit.hour >= 4 && date_time.unit.hour < 8) || +                   (date_time.unit.hour == 8 && date_time.unit.minute == 0); +        case 2: +            return (date_time.unit.hour >= 8 && date_time.unit.hour < 12) || +                   (date_time.unit.hour == 0 && date_time.unit.minute == 0); +        case 3: +            return (date_time.unit.hour >= 0 && date_time.unit.hour < 4) || +                   (date_time.unit.hour == 4 && date_time.unit.minute == 0); +        default: +            return true; +    } +} diff --git a/movement/watch_faces/complication/ships_bell_face.h b/movement/watch_faces/complication/ships_bell_face.h new file mode 100644 index 00000000..dd377317 --- /dev/null +++ b/movement/watch_faces/complication/ships_bell_face.h @@ -0,0 +1,67 @@ +/* + * MIT License + * + * Copyright (c) 2023 buckket + * + * 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 SHIPS_BELL_FACE_H_ +#define SHIPS_BELL_FACE_H_ + +#include "movement.h" + +/* + * A ship's bell complication. + * + * See: https://en.wikipedia.org/wiki/Ship%27s_bell#Simpler_system + * + * Similar to the default hourly signal of the simple_clock_face this complication will use the buzzer to signal + * the time in half-hour intervals according to the scheme mentioned above. + * + * Additionally, the user can specify one of the three watches + * of the standard merchant watch system to only receive signals during this watch. + * + * If no watch is specified all signals are emitted. + * + * Usage: + *   - short press Alarm button: Turn on/off bell + *   - long press Alarm button: Cycle through the watches (All/1/2/3) + */ + +typedef struct { +    bool bell_enabled; +    uint8_t on_watch; +} ships_bell_state_t; + +void ships_bell_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void ships_bell_face_activate(movement_settings_t *settings, void *context); +bool ships_bell_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void ships_bell_face_resign(movement_settings_t *settings, void *context); +bool ships_bell_face_wants_background_task(movement_settings_t *settings, void *context); + +#define ships_bell_face ((const watch_face_t){ \ +    ships_bell_face_setup, \ +    ships_bell_face_activate, \ +    ships_bell_face_loop, \ +    ships_bell_face_resign, \ +    ships_bell_face_wants_background_task, \ +}) + +#endif // SHIPS_BELL_FACE_H_ diff --git a/movement/watch_faces/complication/tarot_face.c b/movement/watch_faces/complication/tarot_face.c index ec42865a..403a92df 100644 --- a/movement/watch_faces/complication/tarot_face.c +++ b/movement/watch_faces/complication/tarot_face.c @@ -179,30 +179,28 @@ static void pick_cards(tarot_state_t *state) {  }  static void display_animation(tarot_state_t *state) { -    if (state->is_picking) { -        if (state->animation_frame == 0) { -            watch_display_string("   ", 7); -            watch_set_pixel(1, 4); -            watch_set_pixel(1, 6); -            state->animation_frame = 1; -        } else if (state->animation_frame == 1) { -            watch_clear_pixel(1, 4); -            watch_clear_pixel(1, 6); -            watch_set_pixel(2, 4); -            watch_set_pixel(0, 6); -            state->animation_frame = 2; -        } else if (state->animation_frame == 2) { -            watch_clear_pixel(2, 4); -            watch_clear_pixel(0, 6); -            watch_set_pixel(2, 5); -            watch_set_pixel(0, 5); -            state->animation_frame = 3; -        } else if (state->animation_frame == 3) { -            state->animation_frame = 0; -            state->is_picking = false; -            movement_request_tick_frequency(1); -            tarot_display(state); -        } +    if (state->animation_frame == 0) { +        watch_display_string("   ", 7); +        watch_set_pixel(1, 4); +        watch_set_pixel(1, 6); +        state->animation_frame = 1; +    } else if (state->animation_frame == 1) { +        watch_clear_pixel(1, 4); +        watch_clear_pixel(1, 6); +        watch_set_pixel(2, 4); +        watch_set_pixel(0, 6); +        state->animation_frame = 2; +    } else if (state->animation_frame == 2) { +        watch_clear_pixel(2, 4); +        watch_clear_pixel(0, 6); +        watch_set_pixel(2, 5); +        watch_set_pixel(0, 5); +        state->animation_frame = 3; +    } else if (state->animation_frame == 3) { +        state->animation_frame = 0; +        state->is_picking = false; +        movement_request_tick_frequency(1); +        tarot_display(state);      }  } @@ -246,7 +244,9 @@ bool tarot_face_loop(movement_event_t event, movement_settings_t *settings, void              tarot_display(state);              break;          case EVENT_TICK: -            display_animation(state); +            if (state->is_picking) { +                display_animation(state); +            }              break;          case EVENT_LIGHT_BUTTON_UP:              if (state->drawn_cards[0] == 0xff) {  | 
