From a4ddd2b0a412c99a9a9415da98ded351226da4ef Mon Sep 17 00:00:00 2001 From: Giorgio Ciacchella Date: Fri, 26 Dec 2025 19:45:16 +0100 Subject: [PATCH 1/9] copy over basic alarm face ref #1 --- movement_faces.h | 1 + watch-faces.mk | 1 + .../complication/watch_radio_alarm_face.c | 213 ++++++++++++++++++ .../complication/watch_radio_alarm_face.h | 62 +++++ 4 files changed, 277 insertions(+) create mode 100644 watch-faces/complication/watch_radio_alarm_face.c create mode 100644 watch-faces/complication/watch_radio_alarm_face.h diff --git a/movement_faces.h b/movement_faces.h index eef0e3acd..18a5f48e6 100644 --- a/movement_faces.h +++ b/movement_faces.h @@ -78,4 +78,5 @@ #include "higher_lower_game_face.h" #include "lander_face.h" #include "simon_face.h" +#include "watch_radio_alarm_face.h" // New includes go above this line. diff --git a/watch-faces.mk b/watch-faces.mk index 3c4249237..dcf6c7ef1 100644 --- a/watch-faces.mk +++ b/watch-faces.mk @@ -53,4 +53,5 @@ SRCS += \ ./watch-faces/complication/higher_lower_game_face.c \ ./watch-faces/complication/lander_face.c \ ./watch-faces/complication/simon_face.c \ + ./watch-faces/complication/watch_radio_alarm_face.c \ # New watch faces go above this line. diff --git a/watch-faces/complication/watch_radio_alarm_face.c b/watch-faces/complication/watch_radio_alarm_face.c new file mode 100644 index 000000000..6013ed152 --- /dev/null +++ b/watch-faces/complication/watch_radio_alarm_face.c @@ -0,0 +1,213 @@ +/* + * MIT License + * + * Copyright (c) 2025 Giorgio Ciacchella + * + * 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 +#include +#include "watch_radio_alarm_face.h" + +// +// Private +// + +static void _watch_radio_alarm_face_display_alarm_time(watch_radio_alarm_face_state_t *state) { + uint8_t hour = state->hour; + + if ( movement_clock_mode_24h() ) + watch_set_indicator(WATCH_INDICATOR_24H); + else { + if ( hour >= 12 ) watch_set_indicator(WATCH_INDICATOR_PM); + else watch_clear_indicator(WATCH_INDICATOR_PM); + hour = hour % 12 ? hour % 12 : 12; + } + + static char lcdbuf[7]; + sprintf(lcdbuf, "%2d%02d ", hour, state->minute); + + watch_display_text(WATCH_POSITION_BOTTOM, lcdbuf); +} + +static inline void button_beep() { + // play a beep as confirmation for a button press (if applicable) + if (movement_button_should_sound()) watch_buzzer_play_note_with_volume(BUZZER_NOTE_C7, 50, movement_button_volume()); +} + +// +// Exported +// + +void watch_radio_alarm_face_setup(uint8_t watch_face_index, void ** context_ptr) { + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(watch_radio_alarm_face_state_t)); + memset(*context_ptr, 0, sizeof(watch_radio_alarm_face_state_t)); + // Do any one-time tasks in here; the inside of this conditional happens only at boot. + } + // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. +} + +void watch_radio_alarm_face_activate(void *context) { + watch_radio_alarm_face_state_t *state = (watch_radio_alarm_face_state_t *)context; + // Handle any tasks related to your watch face coming on screen. + state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE; +} + +bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { + watch_radio_alarm_face_state_t *state = (watch_radio_alarm_face_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + // Show your initial UI here. + watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "ALM", "AL"); + if (state->alarm_is_on) watch_set_indicator(WATCH_INDICATOR_SIGNAL); + watch_set_colon(); + _watch_radio_alarm_face_display_alarm_time(state); + break; + case EVENT_TICK: + // No action needed for tick events in normal mode; we displayed our stuff in EVENT_ACTIVATE. + if (state->setting_mode == WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE) + break; + + // but in settings mode, we need to blink up the parameter we're setting. + _watch_radio_alarm_face_display_alarm_time(state); + if (event.subsecond % 2 == 0) + watch_display_text((state->setting_mode == WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR) ? WATCH_POSITION_HOURS : WATCH_POSITION_MINUTES, " "); + break; + case EVENT_LIGHT_BUTTON_UP: + // You can use the Light button for your own purposes. Note that by default, Movement will also + // illuminate the LED in response to EVENT_LIGHT_BUTTON_DOWN; to suppress that behavior, add an + // empty case for EVENT_LIGHT_BUTTON_DOWN. + break; + case EVENT_LIGHT_BUTTON_DOWN: + switch (state->setting_mode) { + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE: + // If we're not in a setting mode, turn on the LED like normal. + movement_illuminate_led(); + break; + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR: + // If we're setting the hour, advance to minute set mode. + state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE; + break; + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE: + // If we're setting the minute, advance back to normal mode and cancel fast tick. + state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE; + movement_request_tick_frequency(1); + // beep to confirm setting. + button_beep(); + // also turn the alarm on since they just set it. + state->alarm_is_on = 1; + movement_set_alarm_enabled(true); + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + _watch_radio_alarm_face_display_alarm_time(state); + break; + } + break; + case EVENT_ALARM_BUTTON_UP: + // Just in case you have need for another button. + if (state->setting_mode == WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE) { + // in normal mode, toggle alarm on/off. + state->alarm_is_on ^= 1; + if ( state->alarm_is_on ) { + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + movement_set_alarm_enabled(true); + } else { + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + movement_set_alarm_enabled(false); + } + } + break; + case EVENT_ALARM_BUTTON_DOWN: + switch (state->setting_mode) { + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE: + // nothing to do here, alarm toggle is handled in EVENT_ALARM_BUTTON_UP. + break; + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR: + // increment hour, wrap around to 0 at 23. + state->hour = (state->hour + 1) % 24; + break; + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE: + // increment minute, wrap around to 0 at 59. + state->minute = (state->minute + 1) % 60; + break; + } + _watch_radio_alarm_face_display_alarm_time(state); + break; + case EVENT_ALARM_LONG_PRESS: + if (state->setting_mode == WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE) { + // long press in normal mode: move to hour setting mode, request fast tick. + state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR; + movement_request_tick_frequency(4); + button_beep(); + } + break; + case EVENT_BACKGROUND_TASK: + movement_play_alarm(); + // 2022-07-23: Thx @joeycastillo for the dedicated “alarm” signal + break; + case EVENT_TIMEOUT: + // Your watch face will receive this event after a period of inactivity. If it makes sense to resign, + // you may uncomment this line to move back to the first watch face in the list: + movement_move_to_face(0); + break; + case EVENT_LOW_ENERGY_UPDATE: + // If you did not resign in EVENT_TIMEOUT, you can use this event to update the display once a minute. + // Avoid displaying fast-updating values like seconds, since the display won't update again for 60 seconds. + // You should also consider starting the tick animation, to show the wearer that this is sleep mode: + // watch_start_sleep_animation(500); + break; + default: + // Movement's default loop handler will step in for any cases you don't handle above: + // * EVENT_LIGHT_BUTTON_DOWN lights the LED + // * EVENT_MODE_BUTTON_UP moves to the next watch face in the list + // * EVENT_MODE_LONG_PRESS returns to the first watch face (or skips to the secondary watch face, if configured) + // You can override any of these behaviors by adding a case for these events to this switch statement. + return movement_default_loop_handler(event); + } + + // return true if the watch can enter standby mode. Generally speaking, you should always return true. + return true; +} + +void watch_radio_alarm_face_resign(void *context) { + (void) context; + // handle any cleanup before your watch face goes off-screen. +} + +movement_watch_face_advisory_t watch_radio_alarm_face_advise(void *context) { + watch_radio_alarm_face_state_t *state = (watch_radio_alarm_face_state_t *)context; + movement_watch_face_advisory_t retval = { 0 }; + + if ( state->alarm_is_on ) { + watch_date_time_t now = movement_get_local_date_time(); + retval.wants_background_task = (state->hour==now.unit.hour && state->minute==now.unit.minute); + // We’re at the mercy of the advise handler + // In Safari, the emulator triggers at the ›end‹ of the minute + // Converting to Unix timestamps and taking a difference between now and wake + // is not an easy win — because the timestamp for wake has to rely on now + // for its date. So first we’d have to see if the TOD of wake is after that + // of now. If it is, take tomorrow’s date, calculating month and year rollover + // if need be. + } + + return retval; +} diff --git a/watch-faces/complication/watch_radio_alarm_face.h b/watch-faces/complication/watch_radio_alarm_face.h new file mode 100644 index 000000000..596b22245 --- /dev/null +++ b/watch-faces/complication/watch_radio_alarm_face.h @@ -0,0 +1,62 @@ +/* + * MIT License + * + * Copyright (c) 2025 Giorgio Ciacchella + * + * 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. + */ + +#pragma once + +#include "movement.h" + +/* + * WATCH/RADIO ALARM FACE + * + * Currently basic daily alarm clock face. + * + */ + +// enum for setting alarm time +typedef enum { + WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE = 0, + WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR, + WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE +} watch_radio_alarm_face_setting_mode_t; + +typedef struct { + // Anything you need to keep track of, put it here! + uint32_t hour : 5; + uint32_t minute : 6; + uint32_t alarm_is_on : 1; + watch_radio_alarm_face_setting_mode_t setting_mode : 2; +} watch_radio_alarm_face_state_t; + +void watch_radio_alarm_face_setup(uint8_t watch_face_index, void ** context_ptr); +void watch_radio_alarm_face_activate(void *context); +bool watch_radio_alarm_face_loop(movement_event_t event, void *context); +void watch_radio_alarm_face_resign(void *context); + +#define watch_radio_alarm_face ((const watch_face_t){ \ + watch_radio_alarm_face_setup, \ + watch_radio_alarm_face_activate, \ + watch_radio_alarm_face_loop, \ + watch_radio_alarm_face_resign, \ + NULL, \ +}) From 5c18b6b6b58ecaa6fe5b5ffa0d433e702189f420 Mon Sep 17 00:00:00 2001 From: Giorgio Ciacchella Date: Sat, 27 Dec 2025 08:40:21 +0100 Subject: [PATCH 2/9] declare and register `watch_radio_alarm_face_advise` this is needed to trigger the alarm based on the logic contained in its body --- watch-faces/complication/watch_radio_alarm_face.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/watch-faces/complication/watch_radio_alarm_face.h b/watch-faces/complication/watch_radio_alarm_face.h index 596b22245..7b6f0bee1 100644 --- a/watch-faces/complication/watch_radio_alarm_face.h +++ b/watch-faces/complication/watch_radio_alarm_face.h @@ -52,11 +52,12 @@ void watch_radio_alarm_face_setup(uint8_t watch_face_index, void ** context_ptr) void watch_radio_alarm_face_activate(void *context); bool watch_radio_alarm_face_loop(movement_event_t event, void *context); void watch_radio_alarm_face_resign(void *context); +movement_watch_face_advisory_t watch_radio_alarm_face_advise(void *context); #define watch_radio_alarm_face ((const watch_face_t){ \ watch_radio_alarm_face_setup, \ watch_radio_alarm_face_activate, \ watch_radio_alarm_face_loop, \ watch_radio_alarm_face_resign, \ - NULL, \ + watch_radio_alarm_face_advise, \ }) From eb57f1ae5d9ce401e36865fea2a837cbdab52a5c Mon Sep 17 00:00:00 2001 From: Giorgio Ciacchella Date: Sat, 27 Dec 2025 09:00:37 +0100 Subject: [PATCH 3/9] add AMPM/AM/PM alarm periodicity --- .../complication/watch_radio_alarm_face.c | 32 +++++++++++++++---- .../complication/watch_radio_alarm_face.h | 11 ++++++- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/watch-faces/complication/watch_radio_alarm_face.c b/watch-faces/complication/watch_radio_alarm_face.c index 6013ed152..0124cc63e 100644 --- a/watch-faces/complication/watch_radio_alarm_face.c +++ b/watch-faces/complication/watch_radio_alarm_face.c @@ -41,8 +41,9 @@ static void _watch_radio_alarm_face_display_alarm_time(watch_radio_alarm_face_st hour = hour % 12 ? hour % 12 : 12; } + const char periods[][7] = {"AP", "A ", " P"}; static char lcdbuf[7]; - sprintf(lcdbuf, "%2d%02d ", hour, state->minute); + sprintf(lcdbuf, "%2d%02d%s", hour, state->minute, periods[state->period]); watch_display_text(WATCH_POSITION_BOTTOM, lcdbuf); } @@ -90,8 +91,10 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { // but in settings mode, we need to blink up the parameter we're setting. _watch_radio_alarm_face_display_alarm_time(state); - if (event.subsecond % 2 == 0) - watch_display_text((state->setting_mode == WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR) ? WATCH_POSITION_HOURS : WATCH_POSITION_MINUTES, " "); + if (event.subsecond % 2 == 0) { + const watch_position_t blink_positions[] = {0, WATCH_POSITION_HOURS, WATCH_POSITION_MINUTES, WATCH_POSITION_SECONDS}; + watch_display_text(blink_positions[state->setting_mode], " "); + } break; case EVENT_LIGHT_BUTTON_UP: // You can use the Light button for your own purposes. Note that by default, Movement will also @@ -109,7 +112,11 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE; break; case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE: - // If we're setting the minute, advance back to normal mode and cancel fast tick. + // If we're setting the minute, advance to period set mode + state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_PERIOD; + break; + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_PERIOD: + // If we're setting the period, advance back to normal mode and cancel fast tick. state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE; movement_request_tick_frequency(1); // beep to confirm setting. @@ -142,13 +149,17 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { // nothing to do here, alarm toggle is handled in EVENT_ALARM_BUTTON_UP. break; case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR: - // increment hour, wrap around to 0 at 23. - state->hour = (state->hour + 1) % 24; + // increment hour, wrap around to 0 at 11. + state->hour = (state->hour + 1) % 12; break; case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE: // increment minute, wrap around to 0 at 59. state->minute = (state->minute + 1) % 60; break; + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_PERIOD: + // toggle AMPM/AM/PM period, wrap around to AMPM at PM. + state->period = (state->period + 1) % 3; + break; } _watch_radio_alarm_face_display_alarm_time(state); break; @@ -199,7 +210,14 @@ movement_watch_face_advisory_t watch_radio_alarm_face_advise(void *context) { if ( state->alarm_is_on ) { watch_date_time_t now = movement_get_local_date_time(); - retval.wants_background_task = (state->hour==now.unit.hour && state->minute==now.unit.minute); + bool wants_background_task_am = false, wants_background_task_pm = false; + if (state->period != WATCH_RADIO_ALARM_FACE_PERIOD_PM) { + wants_background_task_am = (state->hour==now.unit.hour && state->minute==now.unit.minute); + } + if (state->period != WATCH_RADIO_ALARM_FACE_PERIOD_AM) { + wants_background_task_pm = (state->hour+12==now.unit.hour && state->minute==now.unit.minute); + } + retval.wants_background_task = wants_background_task_am || wants_background_task_pm; // We’re at the mercy of the advise handler // In Safari, the emulator triggers at the ›end‹ of the minute // Converting to Unix timestamps and taking a difference between now and wake diff --git a/watch-faces/complication/watch_radio_alarm_face.h b/watch-faces/complication/watch_radio_alarm_face.h index 7b6f0bee1..f8d18775f 100644 --- a/watch-faces/complication/watch_radio_alarm_face.h +++ b/watch-faces/complication/watch_radio_alarm_face.h @@ -37,13 +37,22 @@ typedef enum { WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE = 0, WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR, - WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE + WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE, + WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_PERIOD } watch_radio_alarm_face_setting_mode_t; +// enum for alarm period +typedef enum { + WATCH_RADIO_ALARM_FACE_PERIOD_AMPM = 0, + WATCH_RADIO_ALARM_FACE_PERIOD_AM, + WATCH_RADIO_ALARM_FACE_PERIOD_PM +} watch_radio_alarm_face_period_t; + typedef struct { // Anything you need to keep track of, put it here! uint32_t hour : 5; uint32_t minute : 6; + watch_radio_alarm_face_period_t period : 2; uint32_t alarm_is_on : 1; watch_radio_alarm_face_setting_mode_t setting_mode : 2; } watch_radio_alarm_face_state_t; From b402c5d4c6623929d47f5fb00e0b71e1989436a6 Mon Sep 17 00:00:00 2001 From: Giorgio Ciacchella Date: Sat, 27 Dec 2025 09:59:04 +0100 Subject: [PATCH 4/9] remove uncovered 24H/PM display --- watch-faces/complication/watch_radio_alarm_face.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/watch-faces/complication/watch_radio_alarm_face.c b/watch-faces/complication/watch_radio_alarm_face.c index 0124cc63e..16e5a5a97 100644 --- a/watch-faces/complication/watch_radio_alarm_face.c +++ b/watch-faces/complication/watch_radio_alarm_face.c @@ -31,20 +31,9 @@ // static void _watch_radio_alarm_face_display_alarm_time(watch_radio_alarm_face_state_t *state) { - uint8_t hour = state->hour; - - if ( movement_clock_mode_24h() ) - watch_set_indicator(WATCH_INDICATOR_24H); - else { - if ( hour >= 12 ) watch_set_indicator(WATCH_INDICATOR_PM); - else watch_clear_indicator(WATCH_INDICATOR_PM); - hour = hour % 12 ? hour % 12 : 12; - } - const char periods[][7] = {"AP", "A ", " P"}; static char lcdbuf[7]; - sprintf(lcdbuf, "%2d%02d%s", hour, state->minute, periods[state->period]); - + sprintf(lcdbuf, "%2d%02d%s", state->hour, state->minute, periods[state->period]); watch_display_text(WATCH_POSITION_BOTTOM, lcdbuf); } From dc2a42237014fd9eb44d08a3dc275c018a6e6585 Mon Sep 17 00:00:00 2001 From: Giorgio Ciacchella Date: Sat, 27 Dec 2025 10:01:53 +0100 Subject: [PATCH 5/9] move period display from seconds position to 24H/PM indicators --- .../complication/watch_radio_alarm_face.c | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/watch-faces/complication/watch_radio_alarm_face.c b/watch-faces/complication/watch_radio_alarm_face.c index 16e5a5a97..9eb247fd3 100644 --- a/watch-faces/complication/watch_radio_alarm_face.c +++ b/watch-faces/complication/watch_radio_alarm_face.c @@ -31,9 +31,22 @@ // static void _watch_radio_alarm_face_display_alarm_time(watch_radio_alarm_face_state_t *state) { - const char periods[][7] = {"AP", "A ", " P"}; + switch(state->period) { + case WATCH_RADIO_ALARM_FACE_PERIOD_AMPM: + watch_clear_indicator(WATCH_INDICATOR_PM); + watch_set_indicator(WATCH_INDICATOR_24H); + break; + case WATCH_RADIO_ALARM_FACE_PERIOD_AM: + watch_clear_indicator(WATCH_INDICATOR_PM); + watch_clear_indicator(WATCH_INDICATOR_24H); + break; + case WATCH_RADIO_ALARM_FACE_PERIOD_PM: + watch_set_indicator(WATCH_INDICATOR_PM); + watch_clear_indicator(WATCH_INDICATOR_24H); + } + static char lcdbuf[7]; - sprintf(lcdbuf, "%2d%02d%s", state->hour, state->minute, periods[state->period]); + sprintf(lcdbuf, "%2d%02d ", state->hour, state->minute); watch_display_text(WATCH_POSITION_BOTTOM, lcdbuf); } @@ -81,8 +94,26 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { // but in settings mode, we need to blink up the parameter we're setting. _watch_radio_alarm_face_display_alarm_time(state); if (event.subsecond % 2 == 0) { - const watch_position_t blink_positions[] = {0, WATCH_POSITION_HOURS, WATCH_POSITION_MINUTES, WATCH_POSITION_SECONDS}; + /* const watch_position_t blink_positions[] = {0, WATCH_POSITION_HOURS, WATCH_POSITION_MINUTES, 0}; watch_display_text(blink_positions[state->setting_mode], " "); + watch_display_text((state->setting_mode == WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR) ? WATCH_POSITION_HOURS : WATCH_POSITION_MINUTES, " "); + */ + switch(state->setting_mode) { + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR: + watch_display_text(WATCH_POSITION_HOURS, " "); + break; + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE: + watch_display_text(WATCH_POSITION_MINUTES, " "); + break; + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_PERIOD: + watch_clear_indicator(WATCH_INDICATOR_LAP); + break; + default: + break; + } + } + else if (state->setting_mode == WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_PERIOD) { + watch_set_indicator(WATCH_INDICATOR_LAP); } break; case EVENT_LIGHT_BUTTON_UP: From a55a3c4e9b5ec7ad1f2f44851cfe9a22298ba90a Mon Sep 17 00:00:00 2001 From: Giorgio Ciacchella Date: Tue, 30 Dec 2025 08:40:29 +0100 Subject: [PATCH 6/9] remove leftover comment --- watch-faces/complication/watch_radio_alarm_face.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/watch-faces/complication/watch_radio_alarm_face.c b/watch-faces/complication/watch_radio_alarm_face.c index 9eb247fd3..4300e0528 100644 --- a/watch-faces/complication/watch_radio_alarm_face.c +++ b/watch-faces/complication/watch_radio_alarm_face.c @@ -94,10 +94,6 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { // but in settings mode, we need to blink up the parameter we're setting. _watch_radio_alarm_face_display_alarm_time(state); if (event.subsecond % 2 == 0) { - /* const watch_position_t blink_positions[] = {0, WATCH_POSITION_HOURS, WATCH_POSITION_MINUTES, 0}; - watch_display_text(blink_positions[state->setting_mode], " "); - watch_display_text((state->setting_mode == WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR) ? WATCH_POSITION_HOURS : WATCH_POSITION_MINUTES, " "); - */ switch(state->setting_mode) { case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR: watch_display_text(WATCH_POSITION_HOURS, " "); From f7bf9f9f81e21d76e448dfbed663d4ce7675026b Mon Sep 17 00:00:00 2001 From: Giorgio Ciacchella Date: Tue, 30 Dec 2025 08:45:20 +0100 Subject: [PATCH 7/9] add periodic chime every 60/30/15 minutes fix #1 --- .../complication/watch_radio_alarm_face.c | 107 +++++++++++++----- .../complication/watch_radio_alarm_face.h | 24 +++- 2 files changed, 97 insertions(+), 34 deletions(-) diff --git a/watch-faces/complication/watch_radio_alarm_face.c b/watch-faces/complication/watch_radio_alarm_face.c index 4300e0528..d8fe4e2c1 100644 --- a/watch-faces/complication/watch_radio_alarm_face.c +++ b/watch-faces/complication/watch_radio_alarm_face.c @@ -31,7 +31,7 @@ // static void _watch_radio_alarm_face_display_alarm_time(watch_radio_alarm_face_state_t *state) { - switch(state->period) { + switch(state->alarm_period) { case WATCH_RADIO_ALARM_FACE_PERIOD_AMPM: watch_clear_indicator(WATCH_INDICATOR_PM); watch_set_indicator(WATCH_INDICATOR_24H); @@ -46,7 +46,7 @@ static void _watch_radio_alarm_face_display_alarm_time(watch_radio_alarm_face_st } static char lcdbuf[7]; - sprintf(lcdbuf, "%2d%02d ", state->hour, state->minute); + sprintf(lcdbuf, "%2d%02d%2d", state->hour, state->minute, chime_periods[state->chime_period_idx]); watch_display_text(WATCH_POSITION_BOTTOM, lcdbuf); } @@ -83,6 +83,7 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { // Show your initial UI here. watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "ALM", "AL"); if (state->alarm_is_on) watch_set_indicator(WATCH_INDICATOR_SIGNAL); + if (state->chime_is_on) watch_set_indicator(WATCH_INDICATOR_BELL); watch_set_colon(); _watch_radio_alarm_face_display_alarm_time(state); break; @@ -101,14 +102,16 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE: watch_display_text(WATCH_POSITION_MINUTES, " "); break; - case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_PERIOD: + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_ALARM_PERIOD: watch_clear_indicator(WATCH_INDICATOR_LAP); break; + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_CHIME_PERIOD: + watch_display_text(WATCH_POSITION_SECONDS, " "); default: break; } } - else if (state->setting_mode == WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_PERIOD) { + else if (state->setting_mode == WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_ALARM_PERIOD) { watch_set_indicator(WATCH_INDICATOR_LAP); } break; @@ -128,19 +131,29 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE; break; case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE: - // If we're setting the minute, advance to period set mode - state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_PERIOD; + // If we're setting the minute, advance to alarm period set mode + state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_ALARM_PERIOD; break; - case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_PERIOD: - // If we're setting the period, advance back to normal mode and cancel fast tick. - state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE; - movement_request_tick_frequency(1); + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_ALARM_PERIOD: + // If we're setting the alarm period, advance to chime period set mode. + state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_CHIME_PERIOD; // beep to confirm setting. button_beep(); // also turn the alarm on since they just set it. state->alarm_is_on = 1; movement_set_alarm_enabled(true); watch_set_indicator(WATCH_INDICATOR_SIGNAL); + break; + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_CHIME_PERIOD: + // If we're setting the chime period, advance back to normal mode and cancel fast tick. + state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE; + movement_request_tick_frequency(1); + // beep to confirm setting. + button_beep(); + // also turn the chime on since they just set it. + state->chime_is_on = 1; + // TODO movement_set_chime_enabled(true); + watch_set_indicator(WATCH_INDICATOR_BELL); _watch_radio_alarm_face_display_alarm_time(state); break; } @@ -148,8 +161,14 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { case EVENT_ALARM_BUTTON_UP: // Just in case you have need for another button. if (state->setting_mode == WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE) { - // in normal mode, toggle alarm on/off. - state->alarm_is_on ^= 1; + // in normal mode, toggle alarm and chime on/off. + uint32_t alarm_chime_on_states = (state->alarm_is_on << 1) + state->chime_is_on; + // increment variable, wrap around to 00 at 11 + alarm_chime_on_states = (alarm_chime_on_states + 1) % 4; + // extract individual bits + state->alarm_is_on = (alarm_chime_on_states & 2) >> 1; + state->chime_is_on = alarm_chime_on_states & 1; + // toggle indicators based on individual bits if ( state->alarm_is_on ) { watch_set_indicator(WATCH_INDICATOR_SIGNAL); movement_set_alarm_enabled(true); @@ -157,6 +176,13 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { watch_clear_indicator(WATCH_INDICATOR_SIGNAL); movement_set_alarm_enabled(false); } + if ( state->chime_is_on ) { + watch_set_indicator(WATCH_INDICATOR_BELL); + // TODO movement_set_chime_enabled(true); + } else { + watch_clear_indicator(WATCH_INDICATOR_BELL); + // TODO movement_set_chime_enabled(false); + } } break; case EVENT_ALARM_BUTTON_DOWN: @@ -172,9 +198,13 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { // increment minute, wrap around to 0 at 59. state->minute = (state->minute + 1) % 60; break; - case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_PERIOD: - // toggle AMPM/AM/PM period, wrap around to AMPM at PM. - state->period = (state->period + 1) % 3; + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_ALARM_PERIOD: + // toggle AMPM/AM/PM alarm period, wrap around to AMPM at PM. + state->alarm_period = (state->alarm_period + 1) % 3; + break; + case WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_CHIME_PERIOD: + // increment chime period according to valid values array. + state->chime_period_idx = (state->chime_period_idx + 1) % 3; break; } _watch_radio_alarm_face_display_alarm_time(state); @@ -188,8 +218,16 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { } break; case EVENT_BACKGROUND_TASK: - movement_play_alarm(); - // 2022-07-23: Thx @joeycastillo for the dedicated “alarm” signal + switch (state->bg_task_type) { + case WATCH_RADIO_ALARM_FACE_BG_TASK_TYPE_ALARM: + movement_play_alarm(); + break; + case WATCH_RADIO_ALARM_FACE_BG_TASK_TYPE_CHIME: + movement_play_signal(); + break; + default: + break; + } break; case EVENT_TIMEOUT: // Your watch face will receive this event after a period of inactivity. If it makes sense to resign, @@ -222,26 +260,37 @@ void watch_radio_alarm_face_resign(void *context) { movement_watch_face_advisory_t watch_radio_alarm_face_advise(void *context) { watch_radio_alarm_face_state_t *state = (watch_radio_alarm_face_state_t *)context; + watch_date_time_t now = movement_get_local_date_time(); movement_watch_face_advisory_t retval = { 0 }; + bool wants_background_task_am = false, wants_background_task_pm = false; if ( state->alarm_is_on ) { - watch_date_time_t now = movement_get_local_date_time(); - bool wants_background_task_am = false, wants_background_task_pm = false; - if (state->period != WATCH_RADIO_ALARM_FACE_PERIOD_PM) { + if (state->alarm_period != WATCH_RADIO_ALARM_FACE_PERIOD_PM) { wants_background_task_am = (state->hour==now.unit.hour && state->minute==now.unit.minute); } - if (state->period != WATCH_RADIO_ALARM_FACE_PERIOD_AM) { + if (state->alarm_period != WATCH_RADIO_ALARM_FACE_PERIOD_AM) { wants_background_task_pm = (state->hour+12==now.unit.hour && state->minute==now.unit.minute); } - retval.wants_background_task = wants_background_task_am || wants_background_task_pm; - // We’re at the mercy of the advise handler - // In Safari, the emulator triggers at the ›end‹ of the minute - // Converting to Unix timestamps and taking a difference between now and wake - // is not an easy win — because the timestamp for wake has to rely on now - // for its date. So first we’d have to see if the TOD of wake is after that - // of now. If it is, take tomorrow’s date, calculating month and year rollover - // if need be. + if (wants_background_task_am || wants_background_task_pm) { + retval.wants_background_task = true; + state->bg_task_type = WATCH_RADIO_ALARM_FACE_BG_TASK_TYPE_ALARM; + } + } + + if ( state->chime_is_on ) { + if (now.unit.minute % chime_periods[state->chime_period_idx] == 0) { + retval.wants_background_task = true; + state->bg_task_type = WATCH_RADIO_ALARM_FACE_BG_TASK_TYPE_CHIME; + } } + // We’re at the mercy of the advise handler + // In Safari, the emulator triggers at the ›end‹ of the minute + // Converting to Unix timestamps and taking a difference between now and wake + // is not an easy win — because the timestamp for wake has to rely on now + // for its date. So first we’d have to see if the TOD of wake is after that + // of now. If it is, take tomorrow’s date, calculating month and year rollover + // if need be. + return retval; } diff --git a/watch-faces/complication/watch_radio_alarm_face.h b/watch-faces/complication/watch_radio_alarm_face.h index f8d18775f..3112b0880 100644 --- a/watch-faces/complication/watch_radio_alarm_face.h +++ b/watch-faces/complication/watch_radio_alarm_face.h @@ -38,7 +38,8 @@ typedef enum { WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE = 0, WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR, WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_MINUTE, - WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_PERIOD + WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_ALARM_PERIOD, + WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_CHIME_PERIOD } watch_radio_alarm_face_setting_mode_t; // enum for alarm period @@ -46,15 +47,28 @@ typedef enum { WATCH_RADIO_ALARM_FACE_PERIOD_AMPM = 0, WATCH_RADIO_ALARM_FACE_PERIOD_AM, WATCH_RADIO_ALARM_FACE_PERIOD_PM -} watch_radio_alarm_face_period_t; +} watch_radio_alarm_face_alarm_period_t; + +// array for chime periods +static const uint32_t chime_periods[] = {60, 30, 15}; + +// enum for background event type +typedef enum { + WATCH_RADIO_ALARM_FACE_BG_TASK_TYPE_NONE = 0, + WATCH_RADIO_ALARM_FACE_BG_TASK_TYPE_ALARM, + WATCH_RADIO_ALARM_FACE_BG_TASK_TYPE_CHIME +} watch_radio_alarm_face_bg_task_type_t; typedef struct { // Anything you need to keep track of, put it here! uint32_t hour : 5; uint32_t minute : 6; - watch_radio_alarm_face_period_t period : 2; - uint32_t alarm_is_on : 1; - watch_radio_alarm_face_setting_mode_t setting_mode : 2; + watch_radio_alarm_face_alarm_period_t alarm_period : 2; + uint32_t alarm_is_on : 2; + uint32_t chime_period_idx : 2; + uint32_t chime_is_on : 2; + watch_radio_alarm_face_bg_task_type_t bg_task_type : 2; + watch_radio_alarm_face_setting_mode_t setting_mode : 3; } watch_radio_alarm_face_state_t; void watch_radio_alarm_face_setup(uint8_t watch_face_index, void ** context_ptr); From 723ba3a10c45d816da5f98c2a546f46e4a505b96 Mon Sep 17 00:00:00 2001 From: Giorgio Ciacchella Date: Tue, 30 Dec 2025 11:05:22 +0100 Subject: [PATCH 8/9] fix LAP indicator occasionally left on --- watch-faces/complication/watch_radio_alarm_face.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/watch-faces/complication/watch_radio_alarm_face.c b/watch-faces/complication/watch_radio_alarm_face.c index d8fe4e2c1..904144744 100644 --- a/watch-faces/complication/watch_radio_alarm_face.c +++ b/watch-faces/complication/watch_radio_alarm_face.c @@ -44,13 +44,14 @@ static void _watch_radio_alarm_face_display_alarm_time(watch_radio_alarm_face_st watch_set_indicator(WATCH_INDICATOR_PM); watch_clear_indicator(WATCH_INDICATOR_24H); } + watch_clear_indicator(WATCH_INDICATOR_LAP); static char lcdbuf[7]; sprintf(lcdbuf, "%2d%02d%2d", state->hour, state->minute, chime_periods[state->chime_period_idx]); watch_display_text(WATCH_POSITION_BOTTOM, lcdbuf); } -static inline void button_beep() { +static inline void _button_beep() { // play a beep as confirmation for a button press (if applicable) if (movement_button_should_sound()) watch_buzzer_play_note_with_volume(BUZZER_NOTE_C7, 50, movement_button_volume()); } @@ -138,7 +139,7 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { // If we're setting the alarm period, advance to chime period set mode. state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_CHIME_PERIOD; // beep to confirm setting. - button_beep(); + _button_beep(); // also turn the alarm on since they just set it. state->alarm_is_on = 1; movement_set_alarm_enabled(true); @@ -149,7 +150,7 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_NONE; movement_request_tick_frequency(1); // beep to confirm setting. - button_beep(); + _button_beep(); // also turn the chime on since they just set it. state->chime_is_on = 1; // TODO movement_set_chime_enabled(true); @@ -214,7 +215,7 @@ bool watch_radio_alarm_face_loop(movement_event_t event, void *context) { // long press in normal mode: move to hour setting mode, request fast tick. state->setting_mode = WATCH_RADIO_ALARM_FACE_SETTING_MODE_SETTING_HOUR; movement_request_tick_frequency(4); - button_beep(); + _button_beep(); } break; case EVENT_BACKGROUND_TASK: From 8a4d05f361121631b112771fa4c01afb149ee5ff Mon Sep 17 00:00:00 2001 From: Giorgio Ciacchella Date: Tue, 30 Dec 2025 11:07:55 +0100 Subject: [PATCH 9/9] optimise field sizes for alarm and chime on/off --- watch-faces/complication/watch_radio_alarm_face.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/watch-faces/complication/watch_radio_alarm_face.h b/watch-faces/complication/watch_radio_alarm_face.h index 3112b0880..5fb41e8df 100644 --- a/watch-faces/complication/watch_radio_alarm_face.h +++ b/watch-faces/complication/watch_radio_alarm_face.h @@ -64,9 +64,9 @@ typedef struct { uint32_t hour : 5; uint32_t minute : 6; watch_radio_alarm_face_alarm_period_t alarm_period : 2; - uint32_t alarm_is_on : 2; + uint32_t alarm_is_on : 1; uint32_t chime_period_idx : 2; - uint32_t chime_is_on : 2; + uint32_t chime_is_on : 1; watch_radio_alarm_face_bg_task_type_t bg_task_type : 2; watch_radio_alarm_face_setting_mode_t setting_mode : 3; } watch_radio_alarm_face_state_t;