Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions movement/make/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ SRCS += \
../watch_faces/sensor/accel_interrupt_count_face.c \
../watch_faces/complication/metronome_face.c \
../watch_faces/complication/smallchess_face.c \
../watch_faces/complication/tap_tempo_face.c \
../watch_faces/complication/stebbs_face.c \
../watch_faces/complication/tiberium_face.c \
../watch_faces/complication/love_alarm_face.c \
../watch_faces/complication/interval_chime_face.c \
../watch_faces/complication/melody_library.c \
../watch_faces/complication/melody_face.c
# New watch faces go above this line.

# Leave this line at the bottom of the file; it has all the targets for making your project.
Expand Down
24 changes: 18 additions & 6 deletions movement/movement_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,26 @@

const watch_face_t watch_faces[] = {
simple_clock_face,
world_clock_face,
stebbs_face,
sunrise_sunset_face,
moon_phase_face,
stopwatch_face,
day_night_percentage_face,
day_one_face,
alarm_face,
interval_chime_face,
stock_stopwatch_face,
countdown_face,
tally_face,
toss_up_face,
tap_tempo_face,
melody_face,
butterfly_game_face,
endless_runner_face,
wyoscan_face,
preferences_face,
set_time_face,
thermistor_readout_face,
voltage_face


};

#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
Expand Down Expand Up @@ -71,7 +83,7 @@ const watch_face_t watch_faces[] = {
* 2: 5 minutes
* 3: 30 minutes
*/
#define MOVEMENT_DEFAULT_TIMEOUT_INTERVAL 0
#define MOVEMENT_DEFAULT_TIMEOUT_INTERVAL 2

/* Set the timeout before switching to low energy mode
* Valid values are:
Expand All @@ -93,6 +105,6 @@ const watch_face_t watch_faces[] = {
* 2: 3 seconds
* 3: 5 seconds
*/
#define MOVEMENT_DEFAULT_LED_DURATION 1
#define MOVEMENT_DEFAULT_LED_DURATION 2

#endif // MOVEMENT_CONFIG_H_
6 changes: 6 additions & 0 deletions movement/movement_faces.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@
#include "accel_interrupt_count_face.h"
#include "metronome_face.h"
#include "smallchess_face.h"
#include "tap_tempo_face.h"
#include "stebbs_face.h"
#include "tiberium_face.h"
#include "love_alarm_face.h"
#include "interval_chime_face.h"
#include "melody_face.h"
// New includes go above this line.

#endif // MOVEMENT_FACES_H_
11 changes: 9 additions & 2 deletions movement/watch_faces/clock/wyoscan_face.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,15 @@ bool wyoscan_face_loop(movement_event_t event, movement_settings_t *settings, vo
state->end = 0;
state->animation = 0;
state->animate = true;
state->time_digits[0] = date_time.unit.hour / 10;
state->time_digits[1] = date_time.unit.hour % 10;
{
uint8_t hour = date_time.unit.hour;
if (!settings->bit.clock_mode_24h) {
hour = hour % 12;
if (hour == 0) hour = 12;
}
state->time_digits[0] = hour / 10;
state->time_digits[1] = hour % 10;
}
state->time_digits[2] = date_time.unit.minute / 10;
state->time_digits[3] = date_time.unit.minute % 10;
state->time_digits[4] = date_time.unit.second / 10;
Expand Down
69 changes: 55 additions & 14 deletions movement/watch_faces/complication/alarm_face.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <string.h>

#include "alarm_face.h"
#include "melody_library.h"
#include "watch.h"
#include "watch_utility.h"
#include "watch_private_display.h"
Expand Down Expand Up @@ -106,12 +107,27 @@ static void _alarm_face_draw(movement_settings_t *settings, alarm_state_t *state
if (state->is_setting) {
// draw pitch level indicator
if ((subsecond % 2) == 0 || (state->setting_state != alarm_setting_idx_pitch)) {
for (i = 0; i <= state->alarm[state->alarm_idx].pitch && i < 3; i++)
watch_set_pixel(_buzzer_segdata[i][0], _buzzer_segdata[i][1]);
if (state->alarm[state->alarm_idx].pitch == 3) {
// melody mode: show bell indicator and all 3 pitch pixels
watch_set_indicator(WATCH_INDICATOR_BELL);
for (i = 0; i < 3; i++)
watch_set_pixel(_buzzer_segdata[i][0], _buzzer_segdata[i][1]);
} else {
watch_clear_indicator(WATCH_INDICATOR_BELL);
for (i = 0; i <= state->alarm[state->alarm_idx].pitch && i < 3; i++)
watch_set_pixel(_buzzer_segdata[i][0], _buzzer_segdata[i][1]);
}
}
// draw beep rounds indicator
// draw beep rounds / melody indicator
if ((subsecond % 2) == 0 || (state->setting_state != alarm_setting_idx_beeps)) {
if (state->alarm[state->alarm_idx].beeps == ALARM_MAX_BEEP_ROUNDS - 1)
if (state->alarm[state->alarm_idx].pitch == 3) {
// melody mode: show melody abbreviation character
uint8_t melody_idx = state->alarm[state->alarm_idx].beeps;
if (melody_idx < MELODY_NUM_TUNES)
watch_display_character(melody_tunes[melody_idx].short_char, _blink_idx[alarm_setting_idx_beeps]);
else
watch_display_character('0', _blink_idx[alarm_setting_idx_beeps]);
} else if (state->alarm[state->alarm_idx].beeps == ALARM_MAX_BEEP_ROUNDS - 1)
watch_display_character('L', _blink_idx[alarm_setting_idx_beeps]);
else {
if (state->alarm[state->alarm_idx].beeps == 0)
Expand Down Expand Up @@ -187,8 +203,13 @@ static void _alarm_play_short_beep(uint8_t pitch_idx) {
}

static void _alarm_indicate_beep(alarm_state_t *state) {
// play an example for the current beep setting
if (state->alarm[state->alarm_idx].beeps == 0) {
if (state->alarm[state->alarm_idx].pitch == 3) {
// melody mode: play a short preview of the selected melody
uint8_t melody_idx = state->alarm[state->alarm_idx].beeps;
if (melody_idx < MELODY_NUM_TUNES) {
watch_buzzer_play_sequence((int8_t *)melody_tunes[melody_idx].sequence, NULL);
}
} else if (state->alarm[state->alarm_idx].beeps == 0) {
// short double beep
_alarm_play_short_beep(state->alarm[state->alarm_idx].pitch);
} else {
Expand Down Expand Up @@ -347,16 +368,27 @@ bool alarm_face_loop(movement_event_t event, movement_settings_t *settings, void
state->alarm[state->alarm_idx].minute = (state->alarm[state->alarm_idx].minute + 1) % 60;
break;
case alarm_setting_idx_pitch:
// pitch level
state->alarm[state->alarm_idx].pitch = (state->alarm[state->alarm_idx].pitch + 1) % 3;
// pitch level: 0-2 = beep pitches, 3 = melody mode
state->alarm[state->alarm_idx].pitch = (state->alarm[state->alarm_idx].pitch + 1) % 4;
if (state->alarm[state->alarm_idx].pitch == 3) {
// entering melody mode: clamp beeps to valid melody index
if (state->alarm[state->alarm_idx].beeps >= MELODY_NUM_TUNES)
state->alarm[state->alarm_idx].beeps = 0;
}
// play sound to show user what this is for
_alarm_indicate_beep(state);
break;
case alarm_setting_idx_beeps:
// number of beeping rounds selection
state->alarm[state->alarm_idx].beeps = (state->alarm[state->alarm_idx].beeps + 1) % ALARM_MAX_BEEP_ROUNDS;
// play sounds when user reaches 'short' length and also one time on regular beep length
if (state->alarm[state->alarm_idx].beeps <= 1) _alarm_indicate_beep(state);
if (state->alarm[state->alarm_idx].pitch == 3) {
// melody mode: cycle through available melodies
state->alarm[state->alarm_idx].beeps = (state->alarm[state->alarm_idx].beeps + 1) % MELODY_NUM_TUNES;
_alarm_indicate_beep(state);
} else {
// number of beeping rounds selection
state->alarm[state->alarm_idx].beeps = (state->alarm[state->alarm_idx].beeps + 1) % ALARM_MAX_BEEP_ROUNDS;
// play sounds when user reaches 'short' length and also one time on regular beep length
if (state->alarm[state->alarm_idx].beeps <= 1) _alarm_indicate_beep(state);
}
break;
default:
break;
Expand Down Expand Up @@ -399,7 +431,16 @@ bool alarm_face_loop(movement_event_t event, movement_settings_t *settings, void
break;
case EVENT_BACKGROUND_TASK:
// play alarm
if (state->alarm[state->alarm_playing_idx].beeps == 0) {
if (state->alarm[state->alarm_playing_idx].pitch == 3) {
// melody mode: play the selected melody
uint8_t melody_idx = state->alarm[state->alarm_playing_idx].beeps;
if (melody_idx < MELODY_NUM_TUNES) {
if (!watch_is_buzzer_or_led_enabled()) {
watch_enable_buzzer();
}
watch_buzzer_play_sequence((int8_t *)melody_tunes[melody_idx].sequence, NULL);
}
} else if (state->alarm[state->alarm_playing_idx].beeps == 0) {
// short beep
if (watch_is_buzzer_or_led_enabled()) {
_alarm_play_short_beep(state->alarm[state->alarm_playing_idx].pitch);
Expand All @@ -411,7 +452,7 @@ bool alarm_face_loop(movement_event_t event, movement_settings_t *settings, void
}
} else {
// regular alarm beeps
movement_play_alarm_beeps((state->alarm[state->alarm_playing_idx].beeps == (ALARM_MAX_BEEP_ROUNDS - 1) ? 20 : state->alarm[state->alarm_playing_idx].beeps),
movement_play_alarm_beeps((state->alarm[state->alarm_playing_idx].beeps == (ALARM_MAX_BEEP_ROUNDS - 1) ? 20 : state->alarm[state->alarm_playing_idx].beeps),
_buzzer_notes[state->alarm[state->alarm_playing_idx].pitch]);
}
// one time alarm? -> erase it
Expand Down
4 changes: 3 additions & 1 deletion movement/watch_faces/complication/countdown_face.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
#include "watch_utility.h"

#define CD_SELECTIONS 3
#define DEFAULT_MINUTES 3
#define DEFAULT_MINUTES 0
#define DEFAULT_SECONDS 30

static bool quick_ticks_running;

Expand Down Expand Up @@ -192,6 +193,7 @@ 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->seconds = DEFAULT_SECONDS;
state->mode = cd_reset;
state->watch_face_index = watch_face_index;
store_countdown(state);
Expand Down
4 changes: 2 additions & 2 deletions movement/watch_faces/complication/day_one_face.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ void day_one_face_setup(movement_settings_t *settings, uint8_t watch_face_index,
// if birth date is totally blank, set a reasonable starting date. this works well for anyone under 63, but
// you can keep pressing to go back to 1900; just pass the year 2080. also picked this date because if you
// set it to 1959-01-02, it counts up from the launch of Luna-1, the first spacecraft to leave the well.
movement_birthdate.bit.year = 1959;
movement_birthdate.bit.month = 1;
movement_birthdate.bit.year = 1989;
movement_birthdate.bit.month = 11;
movement_birthdate.bit.day = 1;
watch_store_backup_data(movement_birthdate.reg, 2);
}
Expand Down
138 changes: 138 additions & 0 deletions movement/watch_faces/complication/interval_chime_face.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* MIT License
*
* Copyright (c) 2026 Stebbs
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "interval_chime_face.h"
#include "watch.h"

static const uint8_t intervals[] = {1, 5, 10, 15, 30};
#define NUM_INTERVALS 5

static void play_power_up_sound(void) {
watch_buzzer_play_note(BUZZER_NOTE_C5, 100);
watch_buzzer_play_note(BUZZER_NOTE_E5, 100);
watch_buzzer_play_note(BUZZER_NOTE_G5, 100);
watch_buzzer_play_note(BUZZER_NOTE_C6, 200);
}

static void play_power_down_sound(void) {
watch_buzzer_play_note(BUZZER_NOTE_C6, 100);
watch_buzzer_play_note(BUZZER_NOTE_G5, 100);
watch_buzzer_play_note(BUZZER_NOTE_E5, 100);
watch_buzzer_play_note(BUZZER_NOTE_C5, 200);
}

static void update_display(interval_chime_state_t *state) {
watch_clear_display();

if (state->is_active) {
char buf[11];
uint8_t interval = intervals[state->interval_index];
sprintf(buf, "rE P0n%02d", interval);
watch_display_string(buf, 0);
watch_set_indicator(WATCH_INDICATOR_BELL);
} else {
watch_display_string("rE P 0FF", 0);
watch_clear_indicator(WATCH_INDICATOR_BELL);
}
}

void interval_chime_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(interval_chime_state_t));
memset(*context_ptr, 0, sizeof(interval_chime_state_t));
}
}

void interval_chime_face_activate(movement_settings_t *settings, void *context) {
(void) settings;
interval_chime_state_t *state = (interval_chime_state_t *)context;

watch_date_time date_time = watch_rtc_get_date_time();
state->last_minute = date_time.unit.minute;

update_display(state);
}

bool interval_chime_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
interval_chime_state_t *state = (interval_chime_state_t *)context;

switch (event.event_type) {
case EVENT_ACTIVATE:
update_display(state);
break;

case EVENT_TICK:
if (state->is_active) {
watch_date_time date_time = watch_rtc_get_date_time();
if (date_time.unit.minute != state->last_minute) {
state->last_minute = date_time.unit.minute;
uint8_t interval = intervals[state->interval_index];
if (date_time.unit.minute % interval == 0) {
movement_play_signal();
}
}
}
break;

case EVENT_ALARM_BUTTON_UP:
state->is_active = !state->is_active;

if (state->is_active) {
play_power_up_sound();
} else {
play_power_down_sound();
}
update_display(state);
break;

case EVENT_LIGHT_BUTTON_UP:
state->interval_index = (state->interval_index + 1) % NUM_INTERVALS;
update_display(state);
movement_illuminate_led();
break;

case EVENT_TIMEOUT:
movement_move_to_face(0);
break;

case EVENT_LOW_ENERGY_UPDATE:
break;

default:
return movement_default_loop_handler(event, settings);
}

return true;
}

void interval_chime_face_resign(movement_settings_t *settings, void *context) {
(void) settings;
(void) context;
}
Loading