diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b4fc7939..84d179a6d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ on: env: COLOR: BLUE -jobs: +jobs: build: container: image: ghcr.io/armmbed/mbed-os-env:latest @@ -29,7 +29,7 @@ jobs: run: make working-directory: 'movement/make' - name: Upload UF2 - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: watch.uf2 path: movement/make/build/watch.uf2 @@ -52,7 +52,7 @@ jobs: cp watch.html index.html tar -czf simulator.tar.gz index.html watch.wasm watch.js - name: Upload simulator build - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: simulator.tar.gz path: movement/make/build-sim/simulator.tar.gz diff --git a/apps/beats-time/app.c b/apps/beats-time/app.c index ef27ffef1..8d6c1db9b 100644 --- a/apps/beats-time/app.c +++ b/apps/beats-time/app.c @@ -2,6 +2,7 @@ #include #include #include "watch.h" +#include "watch_utility.h" const int8_t UTC_OFFSET = 4; // set to your current UTC offset to see correct beats time const uint8_t BEAT_REFRESH_FREQUENCY = 8; @@ -203,7 +204,6 @@ void set_time_mode_handle_primary_button(void) { void set_time_mode_handle_secondary_button(void) { watch_date_time date_time = watch_rtc_get_date_time(); - const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31}; switch (application_state.page) { case 0: // hour @@ -224,13 +224,10 @@ void set_time_mode_handle_secondary_button(void) { break; case 5: // day date_time.unit.day = date_time.unit.day + 1; - // can't set to the 29th on a leap year. if it's february 29, set to 11:59 on the 28th. - // and it should roll over. - if (date_time.unit.day > days_in_month[date_time.unit.month - 1]) { - date_time.unit.day = 1; - } break; } + if (date_time.unit.day > days_in_month(date_time.unit.month, date_time.unit.year + WATCH_RTC_REFERENCE_YEAR)) + date_time.unit.day = 1; watch_rtc_set_date_time(date_time); } diff --git a/movement/movement.c b/movement/movement.c index cb3dcf78e..b2e3066e9 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2022 Joey Castillo + * Copyright (c) 2022 Joey Castillo (edits by Patrick McGuire) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,6 +23,17 @@ */ #define MOVEMENT_LONG_PRESS_TICKS 64 +#define MOVEMENT_REALLY_LONG_PRESS_TICKS 384 +#define DEBOUNCE_TICKS_DOWN 0 +#define DEBOUNCE_TICKS_UP 0 +/* +DEBOUNCE_TICKS_DOWN and DEBOUNCE_TICKS_UP are in terms of fast_cb ticks after a button is pressed. +The logic is that pressed of a button are ignored until the cb_fast_tick function runs this variable amount of times. +Without modifying the code, the cb_fast_tick frequency is 128Hz, or 7.8125ms. +It is not suggested to set this value to one for debouncing, as the callback occurs asynchronously of the button's press, +meaning that if a button was pressed and 7ms passed since th elast time cb_fast_tick was called, then there will be only 812.5us +of debounce time. +*/ #include #include @@ -95,6 +106,31 @@ #define MOVEMENT_DEFAULT_LED_DURATION 1 #endif +// Default to no set location latitude +#ifndef MOVEMENT_DEFAULT_LATITUDE +#define MOVEMENT_DEFAULT_LATITUDE 0 +#endif + +// Default to no set location longitude +#ifndef MOVEMENT_DEFAULT_LONGITUDE +#define MOVEMENT_DEFAULT_LONGITUDE 0 +#endif + +// Default to no set birthdate year +#ifndef MOVEMENT_DEFAULT_BIRTHDATE_YEAR +#define MOVEMENT_DEFAULT_BIRTHDATE_YEAR 0 +#endif + +// Default to no set birthdate month +#ifndef MOVEMENT_DEFAULT_BIRTHDATE_MONTH +#define MOVEMENT_DEFAULT_BIRTHDATE_MONTH 0 +#endif + +// Default to no set birthdate day +#ifndef MOVEMENT_DEFAULT_BIRTHDATE_DAY +#define MOVEMENT_DEFAULT_BIRTHDATE_DAY 0 +#endif + #if __EMSCRIPTEN__ #include #endif @@ -169,6 +205,9 @@ static inline void _movement_reset_inactivity_countdown(void) { static inline void _movement_enable_fast_tick_if_needed(void) { if (!movement_state.fast_tick_enabled) { movement_state.fast_ticks = 0; + movement_state.debounce_ticks_light = 0; + movement_state.debounce_ticks_alarm = 0; + movement_state.debounce_ticks_mode = 0; watch_rtc_register_periodic_callback(cb_fast_tick, 128); movement_state.fast_tick_enabled = true; } @@ -177,6 +216,7 @@ static inline void _movement_enable_fast_tick_if_needed(void) { static inline void _movement_disable_fast_tick_if_possible(void) { if ((movement_state.light_ticks == -1) && (movement_state.alarm_ticks == -1) && + ((movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm) == 0) && ((movement_state.light_down_timestamp + movement_state.mode_down_timestamp + movement_state.alarm_down_timestamp) == 0)) { movement_state.fast_tick_enabled = false; watch_rtc_disable_periodic_callback(128); @@ -201,7 +241,7 @@ static void _movement_handle_scheduled_tasks(void) { for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) { if (scheduled_tasks[i].reg) { - if (scheduled_tasks[i].reg == date_time.reg) { + if (scheduled_tasks[i].reg <= date_time.reg) { scheduled_tasks[i].reg = 0; movement_event_t background_event = { EVENT_BACKGROUND_TASK, 0 }; watch_faces[i].loop(background_event, &movement_state.settings, watch_face_contexts[i]); @@ -328,6 +368,14 @@ static void end_buzzing_and_disable_buzzer(void) { watch_disable_buzzer(); } +static void set_initial_clock_mode(void) { +#ifdef CLOCK_FACE_24H_ONLY + movement_state.settings.bit.clock_mode_24h = true; +#else + movement_state.settings.bit.clock_mode_24h = MOVEMENT_DEFAULT_24H_MODE; +#endif +} + void movement_play_signal(void) { void *maybe_disable_buzzer = end_buzzing_and_disable_buzzer; if (watch_is_buzzer_or_led_enabled()) { @@ -376,14 +424,18 @@ void app_init(void) { #endif memset(&movement_state, 0, sizeof(movement_state)); - - movement_state.settings.bit.clock_mode_24h = MOVEMENT_DEFAULT_24H_MODE; + set_initial_clock_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 = 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.location.bit.latitude = MOVEMENT_DEFAULT_LATITUDE; + movement_state.location.bit.longitude = MOVEMENT_DEFAULT_LONGITUDE; + movement_state.birthdate.bit.year = MOVEMENT_DEFAULT_BIRTHDATE_YEAR; + movement_state.birthdate.bit.month = MOVEMENT_DEFAULT_BIRTHDATE_MONTH; + movement_state.birthdate.bit.day = MOVEMENT_DEFAULT_BIRTHDATE_DAY; movement_state.light_ticks = -1; movement_state.alarm_ticks = -1; movement_state.next_available_backup_register = 4; @@ -406,10 +458,14 @@ void app_init(void) { void app_wake_from_backup(void) { movement_state.settings.reg = watch_get_backup_data(0); + movement_state.location.reg = watch_get_backup_data(1); + movement_state.birthdate.reg = watch_get_backup_data(2); } void app_setup(void) { watch_store_backup_data(movement_state.settings.reg, 0); + watch_store_backup_data(movement_state.location.reg, 1); + watch_store_backup_data(movement_state.birthdate.reg, 2); static bool is_first_launch = true; @@ -462,6 +518,7 @@ void app_wake_from_standby(void) { static void _sleep_mode_app_loop(void) { movement_state.needs_wake = false; + movement_state.ignore_alarm_btn_after_sleep = true; // as long as le_mode_ticks is -1 (i.e. we are in low energy mode), we wake up here, update the screen, and go right back to sleep. while (movement_state.le_mode_ticks == -1) { // we also have to handle background tasks here in the mini-runloop @@ -627,29 +684,69 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e // now that that's out of the way, handle falling edge uint16_t diff = movement_state.fast_ticks - *down_timestamp; *down_timestamp = 0; - _movement_disable_fast_tick_if_possible(); // any press over a half second is considered a long press. Fire the long-up event - if (diff > MOVEMENT_LONG_PRESS_TICKS) return button_down_event_type + 3; - else return button_down_event_type + 1; + if (diff < MOVEMENT_REALLY_LONG_PRESS_TICKS && diff > MOVEMENT_LONG_PRESS_TICKS) { + return button_down_event_type + 3; + } else if (diff > MOVEMENT_REALLY_LONG_PRESS_TICKS) { + return button_down_event_type + 5; + } else return button_down_event_type + 1; + } +} + +static movement_event_type_t btn_action(bool pin_level, int code, uint16_t *timestamp) { + _movement_reset_inactivity_countdown(); + return _figure_out_button_event(pin_level, code, timestamp); +} + +static void light_btn_action(bool pin_level) { + event.event_type = btn_action(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); +} + +static void mode_btn_action(bool pin_level) { + event.event_type = btn_action(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); +} + +static void alarm_btn_action(bool pin_level) { + uint8_t event_type = btn_action(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); + if (movement_state.ignore_alarm_btn_after_sleep){ + if (event_type == EVENT_ALARM_BUTTON_UP || event_type == EVENT_ALARM_LONG_UP) movement_state.ignore_alarm_btn_after_sleep = false; + return; + } + event.event_type = event_type; +} + +static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, uint16_t *down_timestamp, void (*function)(bool)) { + if (*debounce_ticks == 0) { + bool pin_level = watch_get_pin_level(pin); + function(pin_level); + *debounce_ticks = pin_level ? DEBOUNCE_TICKS_DOWN : DEBOUNCE_TICKS_UP; + if (*debounce_ticks != 0) _movement_enable_fast_tick_if_needed(); } + else + *down_timestamp = 0; +} + +static void disable_if_needed(uint8_t *ticks) { + if (*ticks > 0 && --*ticks == 0) + _movement_disable_fast_tick_if_possible(); +} + +static void movement_disable_if_debounce_complete(void) { + disable_if_needed(&movement_state.debounce_ticks_light); + disable_if_needed(&movement_state.debounce_ticks_alarm); + disable_if_needed(&movement_state.debounce_ticks_mode); } void cb_light_btn_interrupt(void) { - bool pin_level = watch_get_pin_level(BTN_LIGHT); - _movement_reset_inactivity_countdown(); - event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); + debounce_btn_press(BTN_LIGHT, &movement_state.debounce_ticks_light, &movement_state.light_down_timestamp, light_btn_action); } void cb_mode_btn_interrupt(void) { - bool pin_level = watch_get_pin_level(BTN_MODE); - _movement_reset_inactivity_countdown(); - event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); + debounce_btn_press(BTN_MODE, &movement_state.debounce_ticks_mode, &movement_state.mode_down_timestamp, mode_btn_action); } void cb_alarm_btn_interrupt(void) { - bool pin_level = watch_get_pin_level(BTN_ALARM); - _movement_reset_inactivity_countdown(); - event.event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); + debounce_btn_press(BTN_ALARM, &movement_state.debounce_ticks_alarm, &movement_state.alarm_down_timestamp, alarm_btn_action); } void cb_alarm_btn_extwake(void) { @@ -662,21 +759,41 @@ void cb_alarm_fired(void) { } void cb_fast_tick(void) { - movement_state.fast_ticks++; + movement_disable_if_debounce_complete(); + if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0) + movement_state.fast_ticks++; if (movement_state.light_ticks > 0) movement_state.light_ticks--; if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--; // check timestamps and auto-fire the long-press events // Notice: is it possible that two or more buttons have an identical timestamp? In this case // only one of these buttons would receive the long press event. Don't bother for now... - if (movement_state.light_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + //if (movement_state.light_down_timestamp > 0) + // if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + // event.event_type = EVENT_LIGHT_LONG_PRESS; + //if (movement_state.mode_down_timestamp > 0) + // if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + // event.event_type = EVENT_MODE_LONG_PRESS; + if (movement_state.light_down_timestamp > 0) { + if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_REALLY_LONG_PRESS_TICKS + 1) { + event.event_type = EVENT_LIGHT_REALLY_LONG_PRESS; + } else if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) { event.event_type = EVENT_LIGHT_LONG_PRESS; - if (movement_state.mode_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + } + } + if (movement_state.mode_down_timestamp > 0) { + if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_REALLY_LONG_PRESS_TICKS + 1) { + event.event_type = EVENT_MODE_REALLY_LONG_PRESS; + } else if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) { event.event_type = EVENT_MODE_LONG_PRESS; - if (movement_state.alarm_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + } + } + if (movement_state.alarm_down_timestamp > 0) { + if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_REALLY_LONG_PRESS_TICKS + 1) { + event.event_type = EVENT_ALARM_REALLY_LONG_PRESS; + } else if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) { event.event_type = EVENT_ALARM_LONG_PRESS; + } + } // this is just a fail-safe; fast tick should be disabled as soon as the button is up, the LED times out, and/or the alarm finishes. // but if for whatever reason it isn't, this forces the fast tick off after 20 seconds. if (movement_state.fast_ticks >= 128 * 20) { diff --git a/movement/movement.h b/movement/movement.h index 1dabfbc5b..956366f1d 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2022 Joey Castillo + * Copyright (c) 2022 Joey Castillo (edits by Patrick McGuire) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -112,14 +112,20 @@ typedef enum { EVENT_LIGHT_BUTTON_UP, // The light button was pressed for less than half a second, and released. EVENT_LIGHT_LONG_PRESS, // The light button was held for over half a second, but not yet released. EVENT_LIGHT_LONG_UP, // The light button was held for over half a second, and released. + EVENT_LIGHT_REALLY_LONG_PRESS, // The light button was held for over 3 seconds, but not yet released. + EVENT_LIGHT_REALLY_LONG_UP, // The light button was held for over 3 second, and released. EVENT_MODE_BUTTON_DOWN, // The mode button has been pressed, but not yet released. EVENT_MODE_BUTTON_UP, // The mode button was pressed for less than half a second, and released. EVENT_MODE_LONG_PRESS, // The mode button was held for over half a second, but not yet released. EVENT_MODE_LONG_UP, // The mode button was held for over half a second, and released. NOTE: your watch face will resign immediately after receiving this event. + EVENT_MODE_REALLY_LONG_PRESS, // The mode button was held for over 3 seconds, but not yet released. + EVENT_MODE_REALLY_LONG_UP, // The mode button was held for over 3 seconds, and released. EVENT_ALARM_BUTTON_DOWN, // The alarm button has been pressed, but not yet released. EVENT_ALARM_BUTTON_UP, // The alarm button was pressed for less than half a second, and released. EVENT_ALARM_LONG_PRESS, // The alarm button was held for over half a second, but not yet released. EVENT_ALARM_LONG_UP, // The alarm button was held for over half a second, and released. + EVENT_ALARM_REALLY_LONG_PRESS, // The alarm button was held for over 3 seconds, but not yet released. + EVENT_ALARM_REALLY_LONG_UP, // The alarm button was held for over 3 seconds, and released. } movement_event_type_t; typedef struct { @@ -242,6 +248,8 @@ typedef struct { typedef struct { // properties stored in BACKUP register movement_settings_t settings; + movement_location_t location; + movement_birthdate_t birthdate; // transient properties int16_t current_face_idx; @@ -270,6 +278,10 @@ typedef struct { // low energy mode countdown int32_t le_mode_ticks; + uint8_t debounce_ticks_light; + uint8_t debounce_ticks_alarm; + uint8_t debounce_ticks_mode; + bool ignore_alarm_btn_after_sleep; // app resignation countdown (TODO: consolidate with LE countdown?) int16_t timeout_ticks; diff --git a/movement/movement_config.h b/movement/movement_config.h index 10a30af77..abceacf1c 100644 --- a/movement/movement_config.h +++ b/movement/movement_config.h @@ -76,15 +76,15 @@ const watch_face_t watch_faces[] = { /* 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 + * 1: 10 mins + * 2: 1 hour + * 3: 2 hours + * 4: 6 hours + * 5: 12 hours + * 6: 1 day * 7: 7 days */ -#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 1 +#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 2 /* Set the led duration * Valid values are: @@ -95,4 +95,20 @@ const watch_face_t watch_faces[] = { */ #define MOVEMENT_DEFAULT_LED_DURATION 1 +/* The latitude and longitude used for the wearers location + * Set signed values in 1/100ths of a degree + */ +#define MOVEMENT_DEFAULT_LATITUDE 0 +#define MOVEMENT_DEFAULT_LONGITUDE 0 + +/* The wearers birthdate + * Valid values: + * Year: 1 - 4095 + * Month: 1 - 12 + * Day: 1 - 31 + */ +#define MOVEMENT_DEFAULT_BIRTHDATE_YEAR 0 +#define MOVEMENT_DEFAULT_BIRTHDATE_MONTH 0 +#define MOVEMENT_DEFAULT_BIRTHDATE_DAY 0 + #endif // MOVEMENT_CONFIG_H_ diff --git a/movement/movement_custom_signal_tunes.h b/movement/movement_custom_signal_tunes.h index cc21a5a8f..2f7ba4011 100644 --- a/movement/movement_custom_signal_tunes.h +++ b/movement/movement_custom_signal_tunes.h @@ -70,18 +70,53 @@ int8_t signal_tune[] = { #ifdef SIGNAL_TUNE_KIM_POSSIBLE int8_t signal_tune[] = { BUZZER_NOTE_G7, 6, - BUZZER_NOTE_REST, 1, - BUZZER_NOTE_G4, 3, + BUZZER_NOTE_G4, 2, BUZZER_NOTE_REST, 5, BUZZER_NOTE_G7, 6, - BUZZER_NOTE_REST, 1, - BUZZER_NOTE_G4, 3, + BUZZER_NOTE_G4, 2, BUZZER_NOTE_REST, 5, BUZZER_NOTE_A7SHARP_B7FLAT, 6, BUZZER_NOTE_REST, 2, BUZZER_NOTE_G7, 6, + BUZZER_NOTE_G4, 2, 0 }; #endif // SIGNAL_TUNE_KIM_POSSIBLE +#ifdef SIGNAL_TUNE_POWER_RANGERS +int8_t signal_tune[] = { + BUZZER_NOTE_D8, 6, + BUZZER_NOTE_REST, 8, + BUZZER_NOTE_D8, 6, + BUZZER_NOTE_REST, 8, + BUZZER_NOTE_C8, 6, + BUZZER_NOTE_REST, 2, + BUZZER_NOTE_D8, 6, + BUZZER_NOTE_REST, 8, + BUZZER_NOTE_F8, 6, + BUZZER_NOTE_REST, 8, + BUZZER_NOTE_D8, 6, + 0 +}; +#endif // SIGNAL_TUNE_POWER_RANGERS + +#ifdef SIGNAL_TUNE_LAYLA +int8_t signal_tune[] = { + BUZZER_NOTE_A6, 5, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_C7, 5, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_D7, 5, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_F7, 5, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_D7, 5, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_C7, 5, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_D7, 20, + 0 +}; +#endif // SIGNAL_TUNE_LAYLA + #endif // MOVEMENT_CUSTOM_SIGNAL_TUNES_H_ diff --git a/movement/watch_faces/clock/clock_face.c b/movement/watch_faces/clock/clock_face.c index eab5cd8d3..20a02e7dc 100644 --- a/movement/watch_faces/clock/clock_face.c +++ b/movement/watch_faces/clock/clock_face.c @@ -42,10 +42,6 @@ #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; @@ -57,8 +53,11 @@ typedef struct { } clock_state_t; static bool clock_is_in_24h_mode(movement_settings_t *settings) { - if (CLOCK_FACE_24H_ONLY) { return true; } +#ifdef CLOCK_FACE_24H_ONLY + return true; +#else return settings->bit.clock_mode_24h; +#endif } static void clock_indicate(WatchIndicatorSegment indicator, bool on) { @@ -70,11 +69,11 @@ static void clock_indicate(WatchIndicatorSegment indicator, bool on) { } static void clock_indicate_alarm(movement_settings_t *settings) { - clock_indicate(WATCH_INDICATOR_BELL, settings->bit.alarm_enabled); + clock_indicate(WATCH_INDICATOR_SIGNAL, settings->bit.alarm_enabled); } static void clock_indicate_time_signal(clock_state_t *clock) { - clock_indicate(WATCH_INDICATOR_SIGNAL, clock->time_signal_enabled); + clock_indicate(WATCH_INDICATOR_BELL, clock->time_signal_enabled); } static void clock_indicate_24h(movement_settings_t *settings) { diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index fbc2c4b3e..ac4ec2ee5 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -51,7 +51,11 @@ void simple_clock_face_activate(movement_settings_t *settings, void *context) { if (watch_tick_animation_is_running()) watch_stop_tick_animation(); +#ifdef CLOCK_FACE_24H_ONLY + watch_set_indicator(WATCH_INDICATOR_24H); +#else if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); +#endif // handle chime indicator if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL); @@ -106,6 +110,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second); } else { // other stuff changed; let's do it all. +#ifndef CLOCK_FACE_24H_ONLY if (!settings->bit.clock_mode_24h) { // if we are in 12 hour mode, do some cleanup. if (date_time.unit.hour < 12) { @@ -116,6 +121,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting date_time.unit.hour %= 12; if (date_time.unit.hour == 0) date_time.unit.hour = 12; } +#endif pos = 0; if (event.event_type == EVENT_LOW_ENERGY_UPDATE) { if (!watch_tick_animation_is_running()) watch_start_tick_animation(500); diff --git a/movement/watch_faces/complication/countdown_face.c b/movement/watch_faces/complication/countdown_face.c index be04040e3..b48ef595a 100644 --- a/movement/watch_faces/complication/countdown_face.c +++ b/movement/watch_faces/complication/countdown_face.c @@ -87,7 +87,10 @@ static void draw(countdown_state_t *state, uint8_t subsecond) { switch (state->mode) { case cd_running: - delta = state->target_ts - state->now_ts; + if (state->target_ts <= state->now_ts) + delta = 0; + else + delta = state->target_ts - state->now_ts; result = div(delta, 60); state->seconds = result.rem; result = div(result.quot, 60); @@ -97,6 +100,7 @@ static void draw(countdown_state_t *state, uint8_t subsecond) { break; case cd_reset: case cd_paused: + watch_clear_indicator(WATCH_INDICATOR_BELL); sprintf(buf, "CD %2d%02d%02d", state->hours, state->minutes, state->seconds); break; case cd_setting: @@ -130,7 +134,6 @@ static void pause(countdown_state_t *state) { static void reset(countdown_state_t *state) { state->mode = cd_reset; movement_cancel_background_task(); - watch_clear_indicator(WATCH_INDICATOR_BELL); load_countdown(state); } diff --git a/movement/watch_faces/complication/day_one_face.c b/movement/watch_faces/complication/day_one_face.c index 27601edcc..aa65321e5 100644 --- a/movement/watch_faces/complication/day_one_face.c +++ b/movement/watch_faces/complication/day_one_face.c @@ -26,8 +26,7 @@ #include #include "day_one_face.h" #include "watch.h" - -static const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31}; +#include "watch_utility.h" static uint32_t _day_one_face_juliandaynum(uint16_t year, uint16_t month, uint16_t day) { // from here: https://en.wikipedia.org/wiki/Julian_day#Julian_day_number_calculation @@ -66,13 +65,12 @@ static void _day_one_face_increment(day_one_state_t *state) { break; case PAGE_DAY: state->birth_day = state->birth_day + 1; - if (state->birth_day == 0 || state->birth_day > days_in_month[state->birth_month - 1]) { - state->birth_day = 1; - } break; default: break; } + if (state->birth_day == 0 || state->birth_day > days_in_month(state->birth_month, state->birth_year)) + state->birth_day = 1; } void day_one_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index ad2f53394..fbf60cfee 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -49,7 +49,7 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s double rise, set, minutes, seconds; bool show_next_match = false; movement_location_t movement_location; - if (state->longLatToUse == 0) + if (state->longLatToUse == 0 || _location_count <= 1) movement_location = (movement_location_t) watch_get_backup_data(1); else{ movement_location.bit.latitude = longLatPresets[state->longLatToUse].latitude; @@ -359,7 +359,7 @@ bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *setti _sunrise_sunset_face_update_location_register(state); } _sunrise_sunset_face_update_settings_display(event, context); - } else if (_location_count == 1) { + } else if (_location_count <= 1) { movement_illuminate_led(); } if (state->page == 0) { @@ -368,7 +368,7 @@ bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *setti } break; case EVENT_LIGHT_LONG_PRESS: - if (_location_count == 1) break; + if (_location_count <= 1) break; else if (!state->page) movement_illuminate_led(); break; case EVENT_LIGHT_BUTTON_UP: diff --git a/movement/watch_faces/complication/time_left_face.c b/movement/watch_faces/complication/time_left_face.c index cc1077aa2..99b0f87fd 100644 --- a/movement/watch_faces/complication/time_left_face.c +++ b/movement/watch_faces/complication/time_left_face.c @@ -27,6 +27,7 @@ #include "time_left_face.h" #include "watch.h" #include "watch_private_display.h" +#include "watch_utility.h" const char _state_titles[][3] = {{'D', 'L', ' '}, {'D', 'L', ' '}, {'D', 'A', ' '}, {'D', 'A', ' '}, {'Y', 'R', 'b'}, {'M', 'O', 'b'}, {'D', 'A', 'b'}, {'Y', 'R', 'd'}, {'M', 'O', 'd'}, {'D', 'A', 'd'}}; @@ -158,8 +159,6 @@ static void _draw(time_left_state_t *state, uint8_t subsecond) { /// @brief handle short or long pressing the alarm button static void _handle_alarm_button(time_left_state_t *state) { - const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31}; - uint32_t tmp_day; switch (state->current_page) { case TIME_LEFT_FACE_SETTINGS_STATE: // birth year state->birth_date.bit.year++; @@ -169,14 +168,7 @@ static void _handle_alarm_button(time_left_state_t *state) { state->birth_date.bit.month = (state->birth_date.bit.month % 12) + 1; break; case TIME_LEFT_FACE_SETTINGS_STATE + 2: // birth day - tmp_day = state->birth_date.bit.day; // use a temporary variable to avoid messing up the months - tmp_day++; - // handle February 29th on a leap year - if (((tmp_day > days_in_month[state->birth_date.bit.month - 1]) && (state->birth_date.bit.month != 2 || (state->birth_date.bit.year % 4) != 0)) - || (state->birth_date.bit.month == 2 && (state->birth_date.bit.year % 4) == 0 && tmp_day > 29)) { - tmp_day = 1; - } - state->birth_date.bit.day = tmp_day; + state->birth_date.bit.day++; break; case TIME_LEFT_FACE_SETTINGS_STATE + 3: // target year state->target_date.bit.year++; @@ -186,16 +178,13 @@ static void _handle_alarm_button(time_left_state_t *state) { state->target_date.bit.month = (state->target_date.bit.month % 12) + 1; break; case TIME_LEFT_FACE_SETTINGS_STATE + 5: // target day - tmp_day = state->target_date.bit.day; - tmp_day++; - // handle February 29th on a leap year - if (((tmp_day > days_in_month[state->target_date.bit.month - 1]) && (state->target_date.bit.month != 2 || (state->target_date.bit.year % 4) != 0)) - || (state->target_date.bit.month == 2 && (state->target_date.bit.year % 4) == 0 && tmp_day > 29)) { - tmp_day = 1; - } - state->target_date.bit.day = tmp_day; + state->target_date.bit.day++; break; } + if (state->birth_date.bit.day > days_in_month(state->birth_date.bit.month, state->birth_date.bit.year)) + state->birth_date.bit.day = 1; + if (state->target_date.bit.day > days_in_month(state->target_date.bit.month, state->birth_date.bit.year)) + state->target_date.bit.day = 1; } static void _initiate_setting(time_left_state_t *state) { diff --git a/movement/watch_faces/complication/totp_face.h b/movement/watch_faces/complication/totp_face.h index 19a2cd452..acdcc8b10 100644 --- a/movement/watch_faces/complication/totp_face.h +++ b/movement/watch_faces/complication/totp_face.h @@ -48,15 +48,19 @@ * o SHA512 * * Instructions: - * o Find your secret key(s) and convert them to the required format. - * o Use https://cryptii.com/pipes/base32-to-hex to convert base32 to hex - * o Use https://github.com/susam/mintotp to generate test codes for verification - * o Edit global variables in "totp_face.c" to configure your stored keys: - * o "keys", "key_sizes", "timesteps", and "algorithms" set the - * cryptographic parameters for each secret key. - * o "labels" sets the two-letter label for each key - * (This replaces the day-of-week indicator) - * o Once finished, remove the two provided examples. + * o Find your secret key(s). + * o Use https://github.com/susam/mintotp to generate test codes for + * verification + * o Edit global `credentials` variable in "totp_face.c" to configure your + * TOTP credentials. The file includes two examples that you can use as a + * reference. Credentials are added with the `CREDENTIAL` macro in the form + * `CREDENTIAL(label, key, algorithm, timestep)` where: + * o `label` is a 2 character label that is displayed in the weekday digits + * to identify the TOTP credential. + * o `key` is a string with the base32 encoded secret. + * o `algorithm` is one of the supported hashing algorithms listed above. + * o `timestep` is how often the TOTP refreshes in seconds. This is usually + * 30 seconds. * * If you have more than one secret key, press ALARM to cycle through them. * Press LIGHT to cycle in the other direction or keep it pressed longer to diff --git a/movement/watch_faces/settings/preferences_face.c b/movement/watch_faces/settings/preferences_face.c index c96e8d1fd..22979773e 100644 --- a/movement/watch_faces/settings/preferences_face.c +++ b/movement/watch_faces/settings/preferences_face.c @@ -26,8 +26,8 @@ #include "preferences_face.h" #include "watch.h" -#define PREFERENCES_FACE_NUM_PREFEFENCES (7) -const char preferences_face_titles[PREFERENCES_FACE_NUM_PREFEFENCES][11] = { +#define PREFERENCES_FACE_NUM_PREFERENCES (7) +const char preferences_face_titles[PREFERENCES_FACE_NUM_PREFERENCES][11] = { "CL ", // Clock: 12 or 24 hour "BT Beep ", // Buttons: should they beep? "TO ", // Timeout: how long before we snap back to the clock face? @@ -65,7 +65,7 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings movement_move_to_next_face(); return false; case EVENT_LIGHT_BUTTON_DOWN: - current_page = (current_page + 1) % PREFERENCES_FACE_NUM_PREFEFENCES; + current_page = (current_page + 1) % PREFERENCES_FACE_NUM_PREFERENCES; *((uint8_t *)context) = current_page; break; case EVENT_ALARM_BUTTON_UP: @@ -99,7 +99,9 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings default: return movement_default_loop_handler(event, settings); } - +#ifdef CLOCK_FACE_24H_ONLY + if (current_page == 0) current_page++; // Skips past 12/24HR mode +#endif watch_display_string((char *)preferences_face_titles[current_page], 0); // blink active setting on even-numbered quarter-seconds diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index a8c88e44a..503dffc99 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -25,6 +25,7 @@ #include #include "set_time_face.h" #include "watch.h" +#include "watch_utility.h" #define SET_TIME_FACE_NUM_SETTINGS (7) const char set_time_face_titles[SET_TIME_FACE_NUM_SETTINGS][3] = {"HR", "M1", "SE", "YR", "MO", "DA", "ZO"}; @@ -33,7 +34,6 @@ static bool _quick_ticks_running; static void _handle_alarm_button(movement_settings_t *settings, watch_date_time date_time, uint8_t current_page) { // handles short or long pressing of the alarm button - const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; switch (current_page) { case 0: // hour @@ -52,14 +52,7 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time date_time.unit.month = (date_time.unit.month % 12) + 1; break; case 5: { // day - uint32_t tmp_day = date_time.unit.day; // use a temporary variable to avoid messing up the months - tmp_day = tmp_day + 1; - // handle February 29th on a leap year - if (((tmp_day > days_in_month[date_time.unit.month - 1]) && (date_time.unit.month != 2 || (date_time.unit.year % 4) != 0)) - || (date_time.unit.month == 2 && (date_time.unit.year % 4) == 0 && tmp_day > 29)) { - tmp_day = 1; - } - date_time.unit.day = tmp_day; + date_time.unit.day = date_time.unit.day + 1; break; } case 6: // time zone @@ -67,6 +60,8 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time if (settings->bit.time_zone > 40) settings->bit.time_zone = 0; break; } + if (date_time.unit.day > days_in_month(date_time.unit.month, date_time.unit.year + WATCH_RTC_REFERENCE_YEAR)) + date_time.unit.day = 1; watch_rtc_set_date_time(date_time); } diff --git a/movement/watch_faces/settings/set_time_hackwatch_face.c b/movement/watch_faces/settings/set_time_hackwatch_face.c index fbe8cbb14..8ba56cba9 100644 --- a/movement/watch_faces/settings/set_time_hackwatch_face.c +++ b/movement/watch_faces/settings/set_time_hackwatch_face.c @@ -26,6 +26,7 @@ #include #include "set_time_hackwatch_face.h" #include "watch.h" +#include "watch_utility.h" char set_time_hackwatch_face_titles[][3] = {"HR", "M1", "SE", "YR", "MO", "DA", "ZO"}; #define set_time_hackwatch_face_NUM_SETTINGS (sizeof(set_time_hackwatch_face_titles) / sizeof(*set_time_hackwatch_face_titles)) @@ -47,7 +48,6 @@ void set_time_hackwatch_face_activate(movement_settings_t *settings, void *conte bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { uint8_t current_page = *((uint8_t *)context); - const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31}; if (event.subsecond == 15) // Delay displayed time update by ~0.5 seconds, to align phase exactly to main clock at 1Hz date_time_settings = watch_rtc_get_date_time(); @@ -119,10 +119,8 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s break; case 5: // day date_time_settings.unit.day = date_time_settings.unit.day - 2; - // can't set to the 29th on a leap year. if it's february 29, set to 11:59 on the 28th. - // and it should roll over. if (date_time_settings.unit.day == 0) { - date_time_settings.unit.day = days_in_month[date_time_settings.unit.month - 1]; + date_time_settings.unit.day = days_in_month(date_time_settings.unit.month, date_time_settings.unit.year + WATCH_RTC_REFERENCE_YEAR); } else date_time_settings.unit.day++; break; @@ -167,17 +165,14 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s break; case 5: // day date_time_settings.unit.day = date_time_settings.unit.day + 1; - // can't set to the 29th on a leap year. if it's february 29, set to 11:59 on the 28th. - // and it should roll over. - if (date_time_settings.unit.day > days_in_month[date_time_settings.unit.month - 1]) { - date_time_settings.unit.day = 1; - } break; case 6: // time zone settings->bit.time_zone++; if (settings->bit.time_zone > 40) settings->bit.time_zone = 0; break; } + if (date_time_settings.unit.day > days_in_month(date_time_settings.unit.month, date_time_settings.unit.year + WATCH_RTC_REFERENCE_YEAR)) + date_time_settings.unit.day = 1; if (current_page != 2) // Do not set time when we are at seconds, it was already set previously watch_rtc_set_date_time(date_time_settings); //TODO: Do not update whole RTC, just what we are changing diff --git a/watch-library/hardware/watch/watch_deepsleep.c b/watch-library/hardware/watch/watch_deepsleep.c index efdad6dda..a25b667b0 100644 --- a/watch-library/hardware/watch/watch_deepsleep.c +++ b/watch-library/hardware/watch/watch_deepsleep.c @@ -77,6 +77,7 @@ void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback, bool le RTC->MODE2.TAMPCTRL.reg = config; // re-enable the RTC RTC->MODE2.CTRLA.bit.ENABLE = 1; + while (RTC->MODE2.SYNCBUSY.bit.ENABLE); // wait for RTC to be enabled NVIC_ClearPendingIRQ(RTC_IRQn); NVIC_EnableIRQ(RTC_IRQn); diff --git a/watch-library/shared/driver/thermistor_driver.c b/watch-library/shared/driver/thermistor_driver.c index 73bdef410..8ce593454 100644 --- a/watch-library/shared/driver/thermistor_driver.c +++ b/watch-library/shared/driver/thermistor_driver.c @@ -45,7 +45,15 @@ void thermistor_driver_disable(void) { // Disable the enable pin's output circuitry. watch_disable_digital_output(THERMISTOR_ENABLE_PIN); } - +#if __EMSCRIPTEN__ +#include +float thermistor_driver_get_temperature(void) +{ + return EM_ASM_DOUBLE({ + return temp_c || 25.0; + }); +} +#else float thermistor_driver_get_temperature(void) { // set the enable pin to the level that powers the thermistor circuit. watch_set_pin_level(THERMISTOR_ENABLE_PIN, THERMISTOR_ENABLE_VALUE); @@ -56,3 +64,4 @@ float thermistor_driver_get_temperature(void) { return watch_utility_thermistor_temperature(value, THERMISTOR_HIGH_SIDE, THERMISTOR_B_COEFFICIENT, THERMISTOR_NOMINAL_TEMPERATURE, THERMISTOR_NOMINAL_RESISTANCE, THERMISTOR_SERIES_RESISTANCE); } +#endif diff --git a/watch-library/shared/watch/watch_private_display.c b/watch-library/shared/watch/watch_private_display.c index c12957d9c..3f15c52c7 100644 --- a/watch-library/shared/watch/watch_private_display.c +++ b/watch-library/shared/watch/watch_private_display.c @@ -43,6 +43,8 @@ void watch_display_character(uint8_t character, uint8_t position) { else if (character == 'M' || character == 'm' || character == 'N') character = 'n'; // M and uppercase N need to be lowercase n else if (character == 'c') character = 'C'; // C needs to be uppercase else if (character == 'J') character = 'j'; // same + else if (character == 't' || character == 'T') character = '+'; // t in those locations looks like E otherwise + else if (character == 'y' || character == 'Y') character = '4'; // y in those locations looks like g otherwise else if (character == 'v' || character == 'V' || character == 'U' || character == 'W' || character == 'w') character = 'u'; // bottom segment duplicated, so show in top half } else { if (character == 'u') character = 'v'; // we can use the bottom segment; move to lower half diff --git a/watch-library/shared/watch/watch_utility.c b/watch-library/shared/watch/watch_utility.c index 64b3bb791..c00791e76 100644 --- a/watch-library/shared/watch/watch_utility.c +++ b/watch-library/shared/watch/watch_utility.c @@ -315,3 +315,11 @@ uint32_t watch_utility_offset_timestamp(uint32_t now, int8_t hours, int8_t minut new += seconds; return new; } + +uint8_t days_in_month(uint8_t month, uint16_t year) { + static const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + uint8_t days = days_in_month[month - 1]; + if (month == 2 && is_leap(year)) + days += 1; + return days; +} diff --git a/watch-library/shared/watch/watch_utility.h b/watch-library/shared/watch/watch_utility.h index e2326d131..5533e1966 100644 --- a/watch-library/shared/watch/watch_utility.h +++ b/watch-library/shared/watch/watch_utility.h @@ -164,4 +164,10 @@ float watch_utility_thermistor_temperature(uint16_t value, bool highside, float */ uint32_t watch_utility_offset_timestamp(uint32_t now, int8_t hours, int8_t minutes, int8_t seconds); +/** @brief Returns the number of days in a month. It also handles Leap Years for February. + * @param month The month of the date (1-12) + * @param year The year of the date (ex. 2022) + */ +uint8_t days_in_month(uint8_t month, uint16_t year); + #endif diff --git a/watch-library/simulator/shell.html b/watch-library/simulator/shell.html index 29fbed038..9cdb29028 100644 --- a/watch-library/simulator/shell.html +++ b/watch-library/simulator/shell.html @@ -905,6 +905,11 @@

Location

+

Temp.

+
+ C + +
@@ -962,6 +967,7 @@

Location

lat = 0; lon = 0; tx = ""; + temp_c = 25.0; function updateLocation(location) { lat = Math.round(location.coords.latitude * 100); lon = Math.round(location.coords.longitude * 100); @@ -1038,10 +1044,25 @@

Location

document.getElementById(skin).checked = true; setSkin(skin); } + + function setTemp() { + let tempInput = document.getElementById("temp-c"); + if (!tempInput) { + return console.warn("no input found"); + } + if (tempInput.value == "") { + return console.warn("no value in input"); + } + + try { + temp_c = Number.parseFloat(tempInput.value); + } catch (e) { + return console.warn("input value is not a valid float:", tempInput.value, e); + } + } loadPrefs(); {{{ SCRIPT }}} -