first build of Second Movement with two watch faces
This commit is contained in:
@@ -1,733 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Joey Castillo
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#define MOVEMENT_LONG_PRESS_TICKS 64
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "watch.h"
|
||||
#include "filesystem.h"
|
||||
#include "movement.h"
|
||||
#include "shell.h"
|
||||
|
||||
#ifndef MOVEMENT_FIRMWARE
|
||||
#include "movement_config.h"
|
||||
#elif MOVEMENT_FIRMWARE == MOVEMENT_FIRMWARE_STANDARD
|
||||
#include "movement_config.h"
|
||||
#elif MOVEMENT_FIRMWARE == MOVEMENT_FIRMWARE_BACKER
|
||||
#include "alt_fw/backer.h"
|
||||
#elif MOVEMENT_FIRMWARE == MOVEMENT_FIRMWARE_ALT_TIME
|
||||
#include "alt_fw/alt_time.h"
|
||||
#elif MOVEMENT_FIRMWARE == MOVEMENT_FIRMWARE_FOCUS
|
||||
#include "alt_fw/focus.h"
|
||||
#elif MOVEMENT_FIRMWARE == MOVEMENT_FIRMWARE_THE_BACKPACKER
|
||||
#include "alt_fw/the_backpacker.h"
|
||||
#elif MOVEMENT_FIRMWARE == MOVEMENT_FIRMWARE_THE_ATHLETE
|
||||
#include "alt_fw/the_athlete.h"
|
||||
#elif MOVEMENT_FIRMWARE == MOVEMENT_FIRMWARE_THE_STARGAZER
|
||||
#include "alt_fw/the_stargazer.h"
|
||||
#elif MOVEMENT_FIRMWARE == MOVEMENT_FIRMWARE_DEEP_SPACE_NOW
|
||||
#include "alt_fw/deep_space_now.h"
|
||||
#endif
|
||||
|
||||
#include "movement_custom_signal_tunes.h"
|
||||
|
||||
// Default to no secondary face behaviour.
|
||||
#ifndef MOVEMENT_SECONDARY_FACE_INDEX
|
||||
#define MOVEMENT_SECONDARY_FACE_INDEX 0
|
||||
#endif
|
||||
|
||||
// Set default LED colors if not set
|
||||
#ifndef MOVEMENT_DEFAULT_RED_COLOR
|
||||
#define MOVEMENT_DEFAULT_RED_COLOR 0x0
|
||||
#endif
|
||||
#ifndef MOVEMENT_DEFAULT_GREEN_COLOR
|
||||
#define MOVEMENT_DEFAULT_GREEN_COLOR 0xF
|
||||
#endif
|
||||
|
||||
// Default to 12h mode
|
||||
#ifndef MOVEMENT_DEFAULT_24H_MODE
|
||||
#define MOVEMENT_DEFAULT_24H_MODE false
|
||||
#endif
|
||||
|
||||
// Default to mode button sounding on press
|
||||
#ifndef MOVEMENT_DEFAULT_BUTTON_SOUND
|
||||
#define MOVEMENT_DEFAULT_BUTTON_SOUND true
|
||||
#endif
|
||||
|
||||
// Default to switch back to main watch face after 60 seconds
|
||||
#ifndef MOVEMENT_DEFAULT_TIMEOUT_INTERVAL
|
||||
#define MOVEMENT_DEFAULT_TIMEOUT_INTERVAL 0
|
||||
#endif
|
||||
|
||||
// Default to switch to low energy mode after 2 hours
|
||||
#ifndef MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL
|
||||
#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 2
|
||||
#endif
|
||||
|
||||
// Default to 1 second led duration
|
||||
#ifndef MOVEMENT_DEFAULT_LED_DURATION
|
||||
#define MOVEMENT_DEFAULT_LED_DURATION 1
|
||||
#endif
|
||||
|
||||
#if __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
movement_state_t movement_state;
|
||||
void * watch_face_contexts[MOVEMENT_NUM_FACES];
|
||||
watch_date_time scheduled_tasks[MOVEMENT_NUM_FACES];
|
||||
const int32_t movement_le_inactivity_deadlines[8] = {INT_MAX, 600, 3600, 7200, 21600, 43200, 86400, 604800};
|
||||
const int16_t movement_timeout_inactivity_deadlines[4] = {60, 120, 300, 1800};
|
||||
movement_event_t event;
|
||||
|
||||
const int16_t movement_timezone_offsets[] = {
|
||||
0, // 0 : 0:00:00 (UTC)
|
||||
60, // 1 : 1:00:00 (Central European Time)
|
||||
120, // 2 : 2:00:00 (South African Standard Time)
|
||||
180, // 3 : 3:00:00 (Arabia Standard Time)
|
||||
210, // 4 : 3:30:00 (Iran Standard Time)
|
||||
240, // 5 : 4:00:00 (Georgia Standard Time)
|
||||
270, // 6 : 4:30:00 (Afghanistan Time)
|
||||
300, // 7 : 5:00:00 (Pakistan Standard Time)
|
||||
330, // 8 : 5:30:00 (Indian Standard Time)
|
||||
345, // 9 : 5:45:00 (Nepal Time)
|
||||
360, // 10 : 6:00:00 (Kyrgyzstan time)
|
||||
390, // 11 : 6:30:00 (Myanmar Time)
|
||||
420, // 12 : 7:00:00 (Thailand Standard Time)
|
||||
480, // 13 : 8:00:00 (China Standard Time, Australian Western Standard Time)
|
||||
525, // 14 : 8:45:00 (Australian Central Western Standard Time)
|
||||
540, // 15 : 9:00:00 (Japan Standard Time, Korea Standard Time)
|
||||
570, // 16 : 9:30:00 (Australian Central Standard Time)
|
||||
600, // 17 : 10:00:00 (Australian Eastern Standard Time)
|
||||
630, // 18 : 10:30:00 (Lord Howe Standard Time)
|
||||
660, // 19 : 11:00:00 (Solomon Islands Time)
|
||||
720, // 20 : 12:00:00 (New Zealand Standard Time)
|
||||
765, // 21 : 12:45:00 (Chatham Standard Time)
|
||||
780, // 22 : 13:00:00 (Tonga Time)
|
||||
825, // 23 : 13:45:00 (Chatham Daylight Time)
|
||||
840, // 24 : 14:00:00 (Line Islands Time)
|
||||
-720, // 25 : -12:00:00 (Baker Island Time)
|
||||
-660, // 26 : -11:00:00 (Niue Time)
|
||||
-600, // 27 : -10:00:00 (Hawaii-Aleutian Standard Time)
|
||||
-570, // 28 : -9:30:00 (Marquesas Islands Time)
|
||||
-540, // 29 : -9:00:00 (Alaska Standard Time)
|
||||
-480, // 30 : -8:00:00 (Pacific Standard Time)
|
||||
-420, // 31 : -7:00:00 (Mountain Standard Time)
|
||||
-360, // 32 : -6:00:00 (Central Standard Time)
|
||||
-300, // 33 : -5:00:00 (Eastern Standard Time)
|
||||
-270, // 34 : -4:30:00 (Venezuelan Standard Time)
|
||||
-240, // 35 : -4:00:00 (Atlantic Standard Time)
|
||||
-210, // 36 : -3:30:00 (Newfoundland Standard Time)
|
||||
-180, // 37 : -3:00:00 (Brasilia Time)
|
||||
-150, // 38 : -2:30:00 (Newfoundland Daylight Time)
|
||||
-120, // 39 : -2:00:00 (Fernando de Noronha Time)
|
||||
-60, // 40 : -1:00:00 (Azores Standard Time)
|
||||
};
|
||||
|
||||
const char movement_valid_position_0_chars[] = " AaBbCcDdEeFGgHhIiJKLMNnOoPQrSTtUuWXYZ-='+\\/0123456789";
|
||||
const char movement_valid_position_1_chars[] = " ABCDEFHlJLNORTtUX-='01378";
|
||||
|
||||
void cb_mode_btn_interrupt(void);
|
||||
void cb_light_btn_interrupt(void);
|
||||
void cb_alarm_btn_interrupt(void);
|
||||
void cb_alarm_btn_extwake(void);
|
||||
void cb_alarm_fired(void);
|
||||
void cb_fast_tick(void);
|
||||
void cb_tick(void);
|
||||
|
||||
static inline void _movement_reset_inactivity_countdown(void) {
|
||||
movement_state.le_mode_ticks = movement_le_inactivity_deadlines[movement_state.settings.bit.le_interval];
|
||||
movement_state.timeout_ticks = movement_timeout_inactivity_deadlines[movement_state.settings.bit.to_interval];
|
||||
}
|
||||
|
||||
static inline void _movement_enable_fast_tick_if_needed(void) {
|
||||
if (!movement_state.fast_tick_enabled) {
|
||||
movement_state.fast_ticks = 0;
|
||||
watch_rtc_register_periodic_callback(cb_fast_tick, 128);
|
||||
movement_state.fast_tick_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void _movement_disable_fast_tick_if_possible(void) {
|
||||
if ((movement_state.light_ticks == -1) &&
|
||||
(movement_state.alarm_ticks == -1) &&
|
||||
((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);
|
||||
}
|
||||
}
|
||||
|
||||
static void _movement_handle_background_tasks(void) {
|
||||
for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) {
|
||||
// For each face, if the watch face wants a background task...
|
||||
if (watch_faces[i].wants_background_task != NULL && watch_faces[i].wants_background_task(&movement_state.settings, watch_face_contexts[i])) {
|
||||
// ...we give it one. pretty straightforward!
|
||||
movement_event_t background_event = { EVENT_BACKGROUND_TASK, 0 };
|
||||
watch_faces[i].loop(background_event, &movement_state.settings, watch_face_contexts[i]);
|
||||
}
|
||||
}
|
||||
movement_state.needs_background_tasks_handled = false;
|
||||
}
|
||||
|
||||
static void _movement_handle_scheduled_tasks(void) {
|
||||
watch_date_time date_time = watch_rtc_get_date_time();
|
||||
uint8_t num_active_tasks = 0;
|
||||
|
||||
for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) {
|
||||
if (scheduled_tasks[i].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]);
|
||||
// check if loop scheduled a new task
|
||||
if (scheduled_tasks[i].reg) {
|
||||
num_active_tasks++;
|
||||
}
|
||||
} else {
|
||||
num_active_tasks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (num_active_tasks == 0) {
|
||||
movement_state.has_scheduled_background_task = false;
|
||||
} else {
|
||||
_movement_reset_inactivity_countdown();
|
||||
}
|
||||
}
|
||||
|
||||
void movement_request_tick_frequency(uint8_t freq) {
|
||||
// Movement uses the 128 Hz tick internally
|
||||
if (freq == 128) return;
|
||||
|
||||
// Movement requires at least a 1 Hz tick.
|
||||
// If we are asked for an invalid frequency, default back to 1 Hz.
|
||||
if (freq == 0 || __builtin_popcount(freq) != 1) freq = 1;
|
||||
|
||||
// disable all callbacks except the 128 Hz one
|
||||
watch_rtc_disable_matching_periodic_callbacks(0xFE);
|
||||
|
||||
movement_state.subsecond = 0;
|
||||
movement_state.tick_frequency = freq;
|
||||
watch_rtc_register_periodic_callback(cb_tick, freq);
|
||||
}
|
||||
|
||||
void movement_illuminate_led(void) {
|
||||
if (movement_state.settings.bit.led_duration != 0b111) {
|
||||
watch_set_led_color(movement_state.settings.bit.led_red_color ? (0xF | movement_state.settings.bit.led_red_color << 4) : 0,
|
||||
movement_state.settings.bit.led_green_color ? (0xF | movement_state.settings.bit.led_green_color << 4) : 0);
|
||||
if (movement_state.settings.bit.led_duration == 0) {
|
||||
movement_state.light_ticks = 1;
|
||||
} else {
|
||||
movement_state.light_ticks = (movement_state.settings.bit.led_duration * 2 - 1) * 128;
|
||||
}
|
||||
_movement_enable_fast_tick_if_needed();
|
||||
}
|
||||
}
|
||||
|
||||
static void _movement_led_off(void) {
|
||||
watch_set_led_off();
|
||||
movement_state.light_ticks = -1;
|
||||
_movement_disable_fast_tick_if_possible();
|
||||
}
|
||||
|
||||
bool movement_default_loop_handler(movement_event_t event, movement_settings_t *settings) {
|
||||
(void)settings;
|
||||
|
||||
switch (event.event_type) {
|
||||
case EVENT_MODE_BUTTON_UP:
|
||||
movement_move_to_next_face();
|
||||
break;
|
||||
case EVENT_LIGHT_BUTTON_DOWN:
|
||||
movement_illuminate_led();
|
||||
break;
|
||||
case EVENT_LIGHT_BUTTON_UP:
|
||||
if (movement_state.settings.bit.led_duration == 0) {
|
||||
_movement_led_off();
|
||||
}
|
||||
break;
|
||||
case EVENT_MODE_LONG_PRESS:
|
||||
if (MOVEMENT_SECONDARY_FACE_INDEX && movement_state.current_face_idx == 0) {
|
||||
movement_move_to_face(MOVEMENT_SECONDARY_FACE_INDEX);
|
||||
} else {
|
||||
movement_move_to_face(0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void movement_move_to_face(uint8_t watch_face_index) {
|
||||
movement_state.watch_face_changed = true;
|
||||
movement_state.next_face_idx = watch_face_index;
|
||||
}
|
||||
|
||||
void movement_move_to_next_face(void) {
|
||||
uint16_t face_max;
|
||||
if (MOVEMENT_SECONDARY_FACE_INDEX) {
|
||||
face_max = (movement_state.current_face_idx < (int16_t)MOVEMENT_SECONDARY_FACE_INDEX) ? MOVEMENT_SECONDARY_FACE_INDEX : MOVEMENT_NUM_FACES;
|
||||
} else {
|
||||
face_max = MOVEMENT_NUM_FACES;
|
||||
}
|
||||
movement_move_to_face((movement_state.current_face_idx + 1) % face_max);
|
||||
}
|
||||
|
||||
void movement_schedule_background_task(watch_date_time date_time) {
|
||||
movement_schedule_background_task_for_face(movement_state.current_face_idx, date_time);
|
||||
}
|
||||
|
||||
void movement_cancel_background_task(void) {
|
||||
movement_cancel_background_task_for_face(movement_state.current_face_idx);
|
||||
}
|
||||
|
||||
void movement_schedule_background_task_for_face(uint8_t watch_face_index, watch_date_time date_time) {
|
||||
watch_date_time now = watch_rtc_get_date_time();
|
||||
if (date_time.reg > now.reg) {
|
||||
movement_state.has_scheduled_background_task = true;
|
||||
scheduled_tasks[watch_face_index].reg = date_time.reg;
|
||||
}
|
||||
}
|
||||
|
||||
void movement_cancel_background_task_for_face(uint8_t watch_face_index) {
|
||||
scheduled_tasks[watch_face_index].reg = 0;
|
||||
bool other_tasks_scheduled = false;
|
||||
for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) {
|
||||
if (scheduled_tasks[i].reg != 0) {
|
||||
other_tasks_scheduled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
movement_state.has_scheduled_background_task = other_tasks_scheduled;
|
||||
}
|
||||
|
||||
void movement_request_wake() {
|
||||
movement_state.needs_wake = true;
|
||||
_movement_reset_inactivity_countdown();
|
||||
}
|
||||
|
||||
static void end_buzzing() {
|
||||
movement_state.is_buzzing = false;
|
||||
}
|
||||
|
||||
static void end_buzzing_and_disable_buzzer(void) {
|
||||
end_buzzing();
|
||||
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()) {
|
||||
maybe_disable_buzzer = end_buzzing;
|
||||
} else {
|
||||
watch_enable_buzzer();
|
||||
}
|
||||
movement_state.is_buzzing = true;
|
||||
watch_buzzer_play_sequence(signal_tune, maybe_disable_buzzer);
|
||||
if (movement_state.le_mode_ticks == -1) {
|
||||
// the watch is asleep. wake it up for "1" round through the main loop.
|
||||
// the sleep_mode_app_loop will notice the is_buzzing and note that it
|
||||
// only woke up to beep and then it will spinlock until the callback
|
||||
// turns off the is_buzzing flag.
|
||||
movement_state.needs_wake = true;
|
||||
movement_state.le_mode_ticks = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void movement_play_alarm(void) {
|
||||
movement_play_alarm_beeps(5, BUZZER_NOTE_C8);
|
||||
}
|
||||
|
||||
void movement_play_alarm_beeps(uint8_t rounds, BuzzerNote alarm_note) {
|
||||
if (rounds == 0) rounds = 1;
|
||||
if (rounds > 20) rounds = 20;
|
||||
movement_request_wake();
|
||||
movement_state.alarm_note = alarm_note;
|
||||
// our tone is 0.375 seconds of beep and 0.625 of silence, repeated as given.
|
||||
movement_state.alarm_ticks = 128 * rounds - 75;
|
||||
_movement_enable_fast_tick_if_needed();
|
||||
}
|
||||
|
||||
uint8_t movement_claim_backup_register(void) {
|
||||
if (movement_state.next_available_backup_register >= 8) return 0;
|
||||
return movement_state.next_available_backup_register++;
|
||||
}
|
||||
|
||||
void app_init(void) {
|
||||
#if defined(NO_FREQCORR)
|
||||
watch_rtc_freqcorr_write(0, 0);
|
||||
#elif defined(WATCH_IS_BLUE_BOARD)
|
||||
watch_rtc_freqcorr_write(11, 0);
|
||||
#else
|
||||
watch_rtc_freqcorr_write(22, 0);
|
||||
#endif
|
||||
|
||||
memset(&movement_state, 0, sizeof(movement_state));
|
||||
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.light_ticks = -1;
|
||||
movement_state.alarm_ticks = -1;
|
||||
movement_state.next_available_backup_register = 4;
|
||||
_movement_reset_inactivity_countdown();
|
||||
|
||||
filesystem_init();
|
||||
|
||||
#if __EMSCRIPTEN__
|
||||
int32_t time_zone_offset = EM_ASM_INT({
|
||||
return -new Date().getTimezoneOffset();
|
||||
});
|
||||
for (int i = 0, count = sizeof(movement_timezone_offsets) / sizeof(movement_timezone_offsets[0]); i < count; i++) {
|
||||
if (movement_timezone_offsets[i] == time_zone_offset) {
|
||||
movement_state.settings.bit.time_zone = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_wake_from_backup(void) {
|
||||
movement_state.settings.reg = watch_get_backup_data(0);
|
||||
}
|
||||
|
||||
void app_setup(void) {
|
||||
watch_store_backup_data(movement_state.settings.reg, 0);
|
||||
|
||||
static bool is_first_launch = true;
|
||||
|
||||
if (is_first_launch) {
|
||||
#ifdef MOVEMENT_CUSTOM_BOOT_COMMANDS
|
||||
MOVEMENT_CUSTOM_BOOT_COMMANDS()
|
||||
#endif
|
||||
|
||||
for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) {
|
||||
watch_face_contexts[i] = NULL;
|
||||
scheduled_tasks[i].reg = 0;
|
||||
is_first_launch = false;
|
||||
}
|
||||
|
||||
// set up the 1 minute alarm (for background tasks and low power updates)
|
||||
watch_date_time alarm_time;
|
||||
alarm_time.reg = 0;
|
||||
alarm_time.unit.second = 59; // after a match, the alarm fires at the next rising edge of CLK_RTC_CNT, so 59 seconds lets us update at :00
|
||||
watch_rtc_register_alarm_callback(cb_alarm_fired, alarm_time, ALARM_MATCH_SS);
|
||||
}
|
||||
if (movement_state.le_mode_ticks != -1) {
|
||||
watch_disable_extwake_interrupt(BTN_ALARM);
|
||||
|
||||
watch_enable_external_interrupts();
|
||||
watch_register_interrupt_callback(BTN_MODE, cb_mode_btn_interrupt, INTERRUPT_TRIGGER_BOTH);
|
||||
watch_register_interrupt_callback(BTN_LIGHT, cb_light_btn_interrupt, INTERRUPT_TRIGGER_BOTH);
|
||||
watch_register_interrupt_callback(BTN_ALARM, cb_alarm_btn_interrupt, INTERRUPT_TRIGGER_BOTH);
|
||||
|
||||
watch_enable_buzzer();
|
||||
watch_enable_leds();
|
||||
watch_enable_display();
|
||||
|
||||
movement_request_tick_frequency(1);
|
||||
|
||||
for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) {
|
||||
watch_faces[i].setup(&movement_state.settings, i, &watch_face_contexts[i]);
|
||||
}
|
||||
|
||||
watch_faces[movement_state.current_face_idx].activate(&movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
|
||||
event.subsecond = 0;
|
||||
event.event_type = EVENT_ACTIVATE;
|
||||
}
|
||||
}
|
||||
|
||||
void app_prepare_for_standby(void) {
|
||||
}
|
||||
|
||||
void app_wake_from_standby(void) {
|
||||
}
|
||||
|
||||
static void _sleep_mode_app_loop(void) {
|
||||
movement_state.needs_wake = false;
|
||||
// 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
|
||||
if (movement_state.needs_background_tasks_handled) _movement_handle_background_tasks();
|
||||
|
||||
event.event_type = EVENT_LOW_ENERGY_UPDATE;
|
||||
watch_faces[movement_state.current_face_idx].loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
|
||||
|
||||
// if we need to wake immediately, do it!
|
||||
if (movement_state.needs_wake) return;
|
||||
// otherwise enter sleep mode, and when the extwake handler is called, it will reset le_mode_ticks and force us out at the next loop.
|
||||
else watch_enter_sleep_mode();
|
||||
}
|
||||
}
|
||||
|
||||
bool app_loop(void) {
|
||||
const watch_face_t *wf = &watch_faces[movement_state.current_face_idx];
|
||||
bool woke_up_for_buzzer = false;
|
||||
if (movement_state.watch_face_changed) {
|
||||
if (movement_state.settings.bit.button_should_sound) {
|
||||
// low note for nonzero case, high note for return to watch_face 0
|
||||
watch_buzzer_play_note(movement_state.next_face_idx ? BUZZER_NOTE_C7 : BUZZER_NOTE_C8, 50);
|
||||
}
|
||||
wf->resign(&movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
|
||||
movement_state.current_face_idx = movement_state.next_face_idx;
|
||||
// we have just updated the face idx, so we must recache the watch face pointer.
|
||||
wf = &watch_faces[movement_state.current_face_idx];
|
||||
watch_clear_display();
|
||||
movement_request_tick_frequency(1);
|
||||
wf->activate(&movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
|
||||
event.subsecond = 0;
|
||||
event.event_type = EVENT_ACTIVATE;
|
||||
movement_state.watch_face_changed = false;
|
||||
}
|
||||
|
||||
// if the LED should be off, turn it off
|
||||
if (movement_state.light_ticks == 0) {
|
||||
// unless the user is holding down the LIGHT button, in which case, give them more time.
|
||||
if (watch_get_pin_level(BTN_LIGHT)) {
|
||||
movement_state.light_ticks = 1;
|
||||
} else {
|
||||
_movement_led_off();
|
||||
}
|
||||
}
|
||||
|
||||
// handle background tasks, if the alarm handler told us we need to
|
||||
if (movement_state.needs_background_tasks_handled) _movement_handle_background_tasks();
|
||||
|
||||
// if we have a scheduled background task, handle that here:
|
||||
if (event.event_type == EVENT_TICK && movement_state.has_scheduled_background_task) _movement_handle_scheduled_tasks();
|
||||
|
||||
// if we have timed out of our low energy mode countdown, enter low energy mode.
|
||||
if (movement_state.le_mode_ticks == 0) {
|
||||
movement_state.le_mode_ticks = -1;
|
||||
watch_register_extwake_callback(BTN_ALARM, cb_alarm_btn_extwake, true);
|
||||
event.event_type = EVENT_NONE;
|
||||
event.subsecond = 0;
|
||||
|
||||
// _sleep_mode_app_loop takes over at this point and loops until le_mode_ticks is reset by the extwake handler,
|
||||
// or wake is requested using the movement_request_wake function.
|
||||
_sleep_mode_app_loop();
|
||||
// as soon as _sleep_mode_app_loop returns, we prepare to reactivate
|
||||
// ourselves, but first, we check to see if we woke up for the buzzer:
|
||||
if (movement_state.is_buzzing) {
|
||||
woke_up_for_buzzer = true;
|
||||
}
|
||||
event.event_type = EVENT_ACTIVATE;
|
||||
// this is a hack tho: waking from sleep mode, app_setup does get called, but it happens before we have reset our ticks.
|
||||
// need to figure out if there's a better heuristic for determining how we woke up.
|
||||
app_setup();
|
||||
}
|
||||
|
||||
// default to being allowed to sleep by the face.
|
||||
bool can_sleep = true;
|
||||
|
||||
if (event.event_type) {
|
||||
event.subsecond = movement_state.subsecond;
|
||||
// the first trip through the loop overrides the can_sleep state
|
||||
can_sleep = wf->loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
|
||||
|
||||
// Keep light on if user is still interacting with the watch.
|
||||
if (movement_state.light_ticks > 0) {
|
||||
switch (event.event_type) {
|
||||
case EVENT_LIGHT_BUTTON_DOWN:
|
||||
case EVENT_MODE_BUTTON_DOWN:
|
||||
case EVENT_ALARM_BUTTON_DOWN:
|
||||
movement_illuminate_led();
|
||||
}
|
||||
}
|
||||
|
||||
event.event_type = EVENT_NONE;
|
||||
}
|
||||
|
||||
// if we have timed out of our timeout countdown, give the app a hint that they can resign.
|
||||
if (movement_state.timeout_ticks == 0) {
|
||||
movement_state.timeout_ticks = -1;
|
||||
if (movement_state.settings.bit.to_always == false) {
|
||||
// if "timeout always" is false, give the current watch face a chance to exit gracefully...
|
||||
event.event_type = EVENT_TIMEOUT;
|
||||
}
|
||||
event.subsecond = movement_state.subsecond;
|
||||
// if we run through the loop again to time out, we need to reconsider whether or not we can sleep.
|
||||
// if the first trip said true, but this trip said false, we need the false to override, thus
|
||||
// we will be using boolean AND:
|
||||
//
|
||||
// first trip | can sleep | cannot sleep | can sleep | cannot sleep
|
||||
// second trip | can sleep | cannot sleep | cannot sleep | can sleep
|
||||
// && | can sleep | cannot sleep | cannot sleep | cannot sleep
|
||||
bool can_sleep2 = wf->loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_face_idx]);
|
||||
can_sleep = can_sleep && can_sleep2;
|
||||
event.event_type = EVENT_NONE;
|
||||
if (movement_state.settings.bit.to_always && movement_state.current_face_idx != 0) {
|
||||
// ...but if the user has "timeout always" set, give it the boot.
|
||||
movement_move_to_face(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we've handled all display update tasks, handle the alarm.
|
||||
if (movement_state.alarm_ticks >= 0) {
|
||||
uint8_t buzzer_phase = (movement_state.alarm_ticks + 80) % 128;
|
||||
if(buzzer_phase == 127) {
|
||||
// failsafe: buzzer could have been disabled in the meantime
|
||||
if (!watch_is_buzzer_or_led_enabled()) watch_enable_buzzer();
|
||||
// play 4 beeps plus pause
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
// TODO: This method of playing the buzzer blocks the UI while it's beeping.
|
||||
// It might be better to time it with the fast tick.
|
||||
watch_buzzer_play_note(movement_state.alarm_note, (i != 3) ? 50 : 75);
|
||||
if (i != 3) watch_buzzer_play_note(BUZZER_NOTE_REST, 50);
|
||||
}
|
||||
}
|
||||
if (movement_state.alarm_ticks == 0) {
|
||||
movement_state.alarm_ticks = -1;
|
||||
_movement_disable_fast_tick_if_possible();
|
||||
}
|
||||
}
|
||||
|
||||
// if we are plugged into USB, handle the serial shell
|
||||
if (watch_is_usb_enabled()) {
|
||||
shell_task();
|
||||
}
|
||||
|
||||
event.subsecond = 0;
|
||||
|
||||
// if the watch face changed, we can't sleep because we need to update the display.
|
||||
if (movement_state.watch_face_changed) can_sleep = false;
|
||||
|
||||
// if we woke up for the buzzer, stay awake until it's finished.
|
||||
if (woke_up_for_buzzer) {
|
||||
while(watch_is_buzzer_or_led_enabled());
|
||||
}
|
||||
|
||||
// if the LED is on, we need to stay awake to keep the TCC running.
|
||||
if (movement_state.light_ticks != -1) can_sleep = false;
|
||||
|
||||
return can_sleep;
|
||||
}
|
||||
|
||||
static movement_event_type_t _figure_out_button_event(bool pin_level, movement_event_type_t button_down_event_type, uint16_t *down_timestamp) {
|
||||
// force alarm off if the user pressed a button.
|
||||
if (movement_state.alarm_ticks) movement_state.alarm_ticks = 0;
|
||||
|
||||
if (pin_level) {
|
||||
// handle rising edge
|
||||
_movement_enable_fast_tick_if_needed();
|
||||
*down_timestamp = movement_state.fast_ticks + 1;
|
||||
return button_down_event_type;
|
||||
} else {
|
||||
// this line is hack but it handles the situation where the light button was held for more than 20 seconds.
|
||||
// fast tick is disabled by then, and the LED would get stuck on since there's no one left decrementing light_ticks.
|
||||
if (movement_state.light_ticks == 1) movement_state.light_ticks = 0;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void cb_alarm_btn_extwake(void) {
|
||||
// wake up!
|
||||
_movement_reset_inactivity_countdown();
|
||||
}
|
||||
|
||||
void cb_alarm_fired(void) {
|
||||
movement_state.needs_background_tasks_handled = true;
|
||||
}
|
||||
|
||||
void cb_fast_tick(void) {
|
||||
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)
|
||||
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.alarm_down_timestamp > 0)
|
||||
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) {
|
||||
watch_rtc_disable_periodic_callback(128);
|
||||
movement_state.fast_tick_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void cb_tick(void) {
|
||||
event.event_type = EVENT_TICK;
|
||||
watch_date_time date_time = watch_rtc_get_date_time();
|
||||
if (date_time.unit.second != movement_state.last_second) {
|
||||
// TODO: can we consolidate these two ticks?
|
||||
if (movement_state.settings.bit.le_interval && movement_state.le_mode_ticks > 0) movement_state.le_mode_ticks--;
|
||||
if (movement_state.timeout_ticks > 0) movement_state.timeout_ticks--;
|
||||
|
||||
movement_state.last_second = date_time.unit.second;
|
||||
movement_state.subsecond = 0;
|
||||
} else {
|
||||
movement_state.subsecond++;
|
||||
}
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Joey Castillo
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MOVEMENT_H_
|
||||
#define MOVEMENT_H_
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include "watch.h"
|
||||
|
||||
// Movement Preferences
|
||||
// These four 32-bit structs store information about the wearer and their preferences. Tentatively, the plan is
|
||||
// for Movement to use four 32-bit registers for these preferences and to store them in the RTC's backup registers
|
||||
// 0-3, leaving registers 4-7 available for third party watch faces to use as they see fit.
|
||||
// * The movement_settings_t struct is provided to all watch faces in the callback functions, and is stored in the
|
||||
// RTC's first backup register (BKUP[0]).
|
||||
// * The movement_location_t and movement_birthdate_t types are defined here, and are tentatively meant to be
|
||||
// stored in BKUP[1] and BKUP[2], respectively.
|
||||
// * The movement_reserved_t type is here as a placeholder, because I sense there's some other generally useful
|
||||
// stuff we'll want to make available to all watch faces and stash in the BKUP[3] register.
|
||||
// This allows these preferences to be stored before entering BACKUP mode and and restored after waking from reset.
|
||||
|
||||
// movement_settings_t contains global settings that cover watch behavior, including preferences around clock and unit
|
||||
// display, time zones, buzzer behavior, LED color and low energy mode timeouts. These settings are stored in BKUP[0].
|
||||
// If your watch face changes one of these settings, you should store your changes in either your loop or resign
|
||||
// function by calling `watch_store_backup_data(settings->reg, 0)`. Otherwise it may not persist after a reset event.
|
||||
typedef union {
|
||||
struct {
|
||||
bool button_should_sound : 1; // if true, pressing a button emits a sound.
|
||||
uint8_t to_interval : 2; // an inactivity interval for asking the active face to resign.
|
||||
bool to_always : 1; // if true, always time out from the active face to face 0. otherwise only faces that time out will resign (the default).
|
||||
uint8_t le_interval : 3; // 0 to disable low energy mode, or an inactivity interval for going into low energy mode.
|
||||
uint8_t led_duration : 3; // how many seconds to shine the LED for (x2), 0 to shine only while the button is depressed, or all bits set to disable the LED altogether.
|
||||
uint8_t led_red_color : 4; // for general purpose illumination, the red LED value (0-15)
|
||||
uint8_t led_green_color : 4; // for general purpose illumination, the green LED value (0-15)
|
||||
uint8_t time_zone : 6; // an integer representing an index in the time zone table.
|
||||
|
||||
// while Movement itself doesn't implement a clock or display units, it may make sense to include some
|
||||
// global settings for watch faces to check. The 12/24 hour preference could inform a clock or a
|
||||
// time-oriented complication like a sunrise/sunset timer, and a simple locale preference could tell an
|
||||
// altimeter to display feet or meters as easily as it tells a thermometer to display degrees in F or C.
|
||||
bool clock_mode_24h : 1; // indicates whether clock should use 12 or 24 hour mode.
|
||||
bool clock_24h_leading_zero : 1; // indicates whether clock should leading zero to indicate 24 hour mode.
|
||||
bool use_imperial_units : 1; // indicates whether to use metric units (the default) or imperial.
|
||||
bool alarm_enabled : 1; // indicates whether there is at least one alarm enabled.
|
||||
uint8_t reserved : 5; // room for more preferences if needed.
|
||||
} bit;
|
||||
uint32_t reg;
|
||||
} movement_settings_t;
|
||||
|
||||
// movement_location_t is for storing the wearer's location. This will be useful for astronomical calculations such as
|
||||
// sunrise and sunset, or predictions of visible satellite passes.
|
||||
// If you create a UI for this register or need to access it, look for it in the RTC's BKUP[1] register.
|
||||
typedef union {
|
||||
struct {
|
||||
int16_t latitude : 16; // signed latutide in hundredths of a degree
|
||||
int16_t longitude : 16; // signed longitude in hundredths of a degree
|
||||
} bit;
|
||||
uint32_t reg;
|
||||
} movement_location_t;
|
||||
|
||||
// movement_birthdate_t is for storing the user's birth date. This will be useful for calculating the user's age — or
|
||||
// hey, playing happy birthday at midnight? Fields for birth time (with hour and minute resolution) are also available,
|
||||
// partly because they fit so nicely, but also because they can be useful for certain astrological calculations.
|
||||
// If you create a UI for birth date or need to access it, look for it in the RTC's BKUP[2] register.
|
||||
typedef union {
|
||||
struct {
|
||||
uint16_t year : 12; // good through the year 4095
|
||||
uint8_t month : 4;
|
||||
uint8_t day : 5;
|
||||
uint8_t hour : 5;
|
||||
uint8_t minute : 6;
|
||||
} bit;
|
||||
uint32_t reg;
|
||||
} movement_birthdate_t;
|
||||
|
||||
// movement_reserved_t is a placeholder for future use of the BKUP[3] register.
|
||||
typedef union {
|
||||
struct {
|
||||
uint32_t reserved : 32;
|
||||
} bit;
|
||||
uint32_t reg;
|
||||
} movement_reserved_t;
|
||||
|
||||
typedef enum {
|
||||
EVENT_NONE = 0, // There is no event to report.
|
||||
EVENT_ACTIVATE, // Your watch face is entering the foreground.
|
||||
EVENT_TICK, // Most common event type. Your watch face is being called from the tick callback.
|
||||
EVENT_LOW_ENERGY_UPDATE, // If the watch is in low energy mode and you are in the foreground, you will get a chance to update the display once per minute.
|
||||
EVENT_BACKGROUND_TASK, // Your watch face is being invoked to perform a background task. Don't update the display here; you may not be in the foreground.
|
||||
EVENT_TIMEOUT, // Your watch face has been inactive for a while. You may want to resign, depending on your watch face's intended use case.
|
||||
EVENT_LIGHT_BUTTON_DOWN, // The light button has been pressed, but not yet released.
|
||||
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_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_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.
|
||||
} movement_event_type_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t event_type;
|
||||
uint8_t subsecond;
|
||||
} movement_event_t;
|
||||
|
||||
extern const int16_t movement_timezone_offsets[];
|
||||
extern const char movement_valid_position_0_chars[];
|
||||
extern const char movement_valid_position_1_chars[];
|
||||
|
||||
/** @brief Perform setup for your watch face.
|
||||
* @details It's tempting to say this is 'one-time' setup, but technically this function is called more than
|
||||
* once. When the watch first boots, this function is called with a NULL context_ptr, indicating
|
||||
* that it is the first run. At this time you should set context_ptr to something non-NULL if you
|
||||
* need to keep track of any state in your watch face. If your watch face requires any other setup,
|
||||
* like configuring a pin mode or a peripheral, you may want to do that here too.
|
||||
* This function will be called again after waking from sleep mode, since sleep mode disables all
|
||||
* of the device's pins and peripherals.
|
||||
* @param settings A pointer to the global Movement settings. You can use this to inform how you present your
|
||||
* display to the user (i.e. taking into account whether they have silenced the buttons, or if
|
||||
* they prefer 12 or 24-hour mode). You can also change these settings if you like.
|
||||
* @param watch_face_index The index of this watch face in the global array of watch faces; 0 is the first face,
|
||||
* 1 is the second, etc. You may stash this value in your context if you wish to reference
|
||||
* it later; your watch face's index is set at launch and will not change.
|
||||
* @param context_ptr A pointer to a pointer; at first invocation, this value will be NULL, and you can set it
|
||||
* to any value you like. Subsequent invocations will pass in whatever value you previously
|
||||
* set. You may want to check if this is NULL and if so, allocate some space to store any
|
||||
* data required for your watch face.
|
||||
*
|
||||
*/
|
||||
typedef void (*watch_face_setup)(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||
|
||||
/** @brief Prepare to go on-screen.
|
||||
* @details This function is called just before your watch enters the foreground. If your watch face has any
|
||||
* segments or text that is always displayed, you may want to set that here. In addition, if your
|
||||
* watch face depends on data from a peripheral (like an I2C sensor), you will likely want to enable
|
||||
* that peripheral here. In addition, if your watch face requires an update frequncy other than 1 Hz,
|
||||
* you may want to request that here using the movement_request_tick_frequency function.
|
||||
* @param settings A pointer to the global Movement settings. @see watch_face_setup.
|
||||
* @param context A pointer to your watch face's context. @see watch_face_setup.
|
||||
*
|
||||
*/
|
||||
typedef void (*watch_face_activate)(movement_settings_t *settings, void *context);
|
||||
|
||||
/** @brief Handle events and update the display.
|
||||
* @details This function is called in response to an event. You should set up a switch statement that handles,
|
||||
* at the very least, the EVENT_TICK and EVENT_MODE_BUTTON_UP event types. The tick event happens once
|
||||
* per second (or more frequently if you asked for a faster tick with movement_request_tick_frequency).
|
||||
* The mode button up event occurs when the user presses the MODE button. **Your loop function SHOULD
|
||||
* call the movement_move_to_next_face function in response to this event.** If you have a good reason
|
||||
* to override this behavior (e.g. your user interface requires all three buttons), your watch face MUST
|
||||
* call the movement_move_to_next_face function in response to the EVENT_MODE_LONG_PRESS event. If you
|
||||
* fail to do this, the user will become stuck on your watch face.
|
||||
* @param event A struct containing information about the event, including its type. @see movement_event_type_t
|
||||
* for a list of all possible event types.
|
||||
* @param settings A pointer to the global Movement settings. @see watch_face_setup.
|
||||
* @param context A pointer to your application's context. @see watch_face_setup.
|
||||
* @return true if your watch face is prepared for the system to enter STANDBY mode; false to keep the system awake.
|
||||
* You should almost always return true.
|
||||
* Note that this return value has no effect if your loop function has called movement_move_to_next_face
|
||||
* or movement_move_to_face; in that case, your watch face will resign immediately, and the next watch
|
||||
* face will make the decision on entering standby mode.
|
||||
* @note There are two event types that require some extra thought:
|
||||
The EVENT_LOW_ENERGY_UPDATE event type is a special case. If you are in the foreground when the watch
|
||||
goes into low energy mode, you will receive this tick once a minute (at the top of the minute) so that
|
||||
you can update the screen. Great! But! When you receive this event, all pins and peripherals other than
|
||||
the RTC will have been disabled to save energy. If your display is clock or calendar oriented, this is
|
||||
fine. But if your display requires polling an I2C sensor or reading a value with the ADC, you won't be
|
||||
able to do this. You should either display the name of the watch face in response to the low power tick,
|
||||
or ensure that you resign before low power mode triggers, (e.g. by calling movement_move_to_face(0)).
|
||||
**Your watch face MUST NOT wake up peripherals in response to a low power tick.** The purpose of this
|
||||
mode is to consume as little energy as possible during the (potentially long) intervals when it's
|
||||
unlikely the user is wearing or looking at the watch.
|
||||
EVENT_BACKGROUND_TASK is also a special case. @see watch_face_wants_background_task for details.
|
||||
*/
|
||||
typedef bool (*watch_face_loop)(movement_event_t event, movement_settings_t *settings, void *context);
|
||||
|
||||
/** @brief Prepare to go off-screen.
|
||||
* @details This function is called before your watch face enters the background. If you requested a tick
|
||||
* frequency other than the standard 1 Hz, **you must call movement_request_tick_frequency(1) here**
|
||||
* to reset to 1 Hz. You should also disable any peripherals you enabled when you entered the foreground.
|
||||
* @param settings A pointer to the global Movement settings. @see watch_face_setup.
|
||||
* @param context A pointer to your application's context. @see watch_face_setup.
|
||||
*/
|
||||
typedef void (*watch_face_resign)(movement_settings_t *settings, void *context);
|
||||
|
||||
/** @brief OPTIONAL. Request an opportunity to run a background task.
|
||||
* @details Most apps will not need this function, but if you provide it, Movement will call it once per minute in
|
||||
* both active and low power modes, regardless of whether your app is in the foreground. You can check the
|
||||
* current time to determine whether you require a background task. If you return true here, Movement will
|
||||
* immediately call your loop function with an EVENT_BACKGROUND_TASK event. Note that it will not call your
|
||||
* activate or deactivate functions, since you are not going on screen.
|
||||
*
|
||||
* Examples of background tasks:
|
||||
* - Wake and play a sound when an alarm or timer has been triggered.
|
||||
* - Check the state of an RTC interrupt pin or the timestamp of an RTC interrupt event.
|
||||
* - Log a data point from a sensor, and then return to sleep mode.
|
||||
*
|
||||
* Guidelines for background tasks:
|
||||
* - Assume all peripherals and pins other than the RTC will be disabled when you get an EVENT_BACKGROUND_TASK.
|
||||
* - Even if your background task involves only the RTC peripheral, try to request background tasks sparingly.
|
||||
* - If your background task involves an external pin or peripheral, request background tasks no more than once per hour.
|
||||
* - If you need to enable a pin or a peripheral to perform your task, return it to its original state afterwards.
|
||||
*
|
||||
* @param settings A pointer to the global Movement settings. @see watch_face_setup.
|
||||
* @param context A pointer to your application's context. @see watch_face_setup.
|
||||
* @return true to request a background task; false otherwise.
|
||||
*/
|
||||
typedef bool (*watch_face_wants_background_task)(movement_settings_t *settings, void *context);
|
||||
|
||||
typedef struct {
|
||||
watch_face_setup setup;
|
||||
watch_face_activate activate;
|
||||
watch_face_loop loop;
|
||||
watch_face_resign resign;
|
||||
watch_face_wants_background_task wants_background_task;
|
||||
} watch_face_t;
|
||||
|
||||
typedef struct {
|
||||
// properties stored in BACKUP register
|
||||
movement_settings_t settings;
|
||||
|
||||
// transient properties
|
||||
int16_t current_face_idx;
|
||||
int16_t next_face_idx;
|
||||
bool watch_face_changed;
|
||||
bool fast_tick_enabled;
|
||||
int16_t fast_ticks;
|
||||
|
||||
// LED stuff
|
||||
int16_t light_ticks;
|
||||
|
||||
// alarm stuff
|
||||
int16_t alarm_ticks;
|
||||
bool is_buzzing;
|
||||
BuzzerNote alarm_note;
|
||||
|
||||
// button tracking for long press
|
||||
uint16_t light_down_timestamp;
|
||||
uint16_t mode_down_timestamp;
|
||||
uint16_t alarm_down_timestamp;
|
||||
|
||||
// background task handling
|
||||
bool needs_background_tasks_handled;
|
||||
bool has_scheduled_background_task;
|
||||
bool needs_wake;
|
||||
|
||||
// low energy mode countdown
|
||||
int32_t le_mode_ticks;
|
||||
|
||||
// app resignation countdown (TODO: consolidate with LE countdown?)
|
||||
int16_t timeout_ticks;
|
||||
|
||||
// stuff for subsecond tracking
|
||||
uint8_t tick_frequency;
|
||||
uint8_t last_second;
|
||||
uint8_t subsecond;
|
||||
|
||||
// backup register stuff
|
||||
uint8_t next_available_backup_register;
|
||||
} movement_state_t;
|
||||
|
||||
void movement_move_to_face(uint8_t watch_face_index);
|
||||
void movement_move_to_next_face(void);
|
||||
|
||||
bool movement_default_loop_handler(movement_event_t event, movement_settings_t *settings);
|
||||
|
||||
void movement_illuminate_led(void);
|
||||
|
||||
void movement_request_tick_frequency(uint8_t freq);
|
||||
|
||||
// note: watch faces can only schedule a background task when in the foreground, since
|
||||
// movement will associate the scheduled task with the currently active face.
|
||||
void movement_schedule_background_task(watch_date_time date_time);
|
||||
|
||||
// note: watch faces can only cancel their background task when in the foreground, since
|
||||
// movement will associate the scheduled task with the currently active face.
|
||||
void movement_cancel_background_task(void);
|
||||
|
||||
// these functions should work around the limitation of the above functions, which will be deprecated.
|
||||
void movement_schedule_background_task_for_face(uint8_t watch_face_index, watch_date_time date_time);
|
||||
void movement_cancel_background_task_for_face(uint8_t watch_face_index);
|
||||
|
||||
void movement_request_wake(void);
|
||||
|
||||
void movement_play_signal(void);
|
||||
void movement_play_alarm(void);
|
||||
void movement_play_alarm_beeps(uint8_t rounds, BuzzerNote alarm_note);
|
||||
|
||||
uint8_t movement_claim_backup_register(void);
|
||||
|
||||
#endif // MOVEMENT_H_
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Joey Castillo
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "simple_clock_face.h"
|
||||
#include "watch.h"
|
||||
#include "watch_utility.h"
|
||||
#include "watch_private_display.h"
|
||||
|
||||
static void _update_alarm_indicator(bool settings_alarm_enabled, simple_clock_state_t *state) {
|
||||
state->alarm_enabled = settings_alarm_enabled;
|
||||
if (state->alarm_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
else watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
}
|
||||
|
||||
void simple_clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
|
||||
(void) settings;
|
||||
(void) watch_face_index;
|
||||
|
||||
if (*context_ptr == NULL) {
|
||||
*context_ptr = malloc(sizeof(simple_clock_state_t));
|
||||
simple_clock_state_t *state = (simple_clock_state_t *)*context_ptr;
|
||||
state->signal_enabled = false;
|
||||
state->watch_face_index = watch_face_index;
|
||||
}
|
||||
}
|
||||
|
||||
void simple_clock_face_activate(movement_settings_t *settings, void *context) {
|
||||
simple_clock_state_t *state = (simple_clock_state_t *)context;
|
||||
|
||||
if (watch_tick_animation_is_running()) watch_stop_tick_animation();
|
||||
|
||||
#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);
|
||||
else watch_clear_indicator(WATCH_INDICATOR_BELL);
|
||||
|
||||
// show alarm indicator if there is an active alarm
|
||||
_update_alarm_indicator(settings->bit.alarm_enabled, state);
|
||||
|
||||
watch_set_colon();
|
||||
|
||||
// this ensures that none of the timestamp fields will match, so we can re-render them all.
|
||||
state->previous_date_time = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
bool simple_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
|
||||
simple_clock_state_t *state = (simple_clock_state_t *)context;
|
||||
char buf[11];
|
||||
uint8_t pos;
|
||||
|
||||
watch_date_time date_time;
|
||||
uint32_t previous_date_time;
|
||||
switch (event.event_type) {
|
||||
case EVENT_ACTIVATE:
|
||||
case EVENT_TICK:
|
||||
case EVENT_LOW_ENERGY_UPDATE:
|
||||
date_time = watch_rtc_get_date_time();
|
||||
previous_date_time = state->previous_date_time;
|
||||
state->previous_date_time = date_time.reg;
|
||||
|
||||
// check the battery voltage once a day...
|
||||
if (date_time.unit.day != state->last_battery_check) {
|
||||
state->last_battery_check = date_time.unit.day;
|
||||
watch_enable_adc();
|
||||
uint16_t voltage = watch_get_vcc_voltage();
|
||||
watch_disable_adc();
|
||||
// 2.2 volts will happen when the battery has maybe 5-10% remaining?
|
||||
// we can refine this later.
|
||||
state->battery_low = (voltage < 2200);
|
||||
}
|
||||
|
||||
// ...and set the LAP indicator if low.
|
||||
if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP);
|
||||
|
||||
bool set_leading_zero = false;
|
||||
if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
|
||||
// everything before seconds is the same, don't waste cycles setting those segments.
|
||||
watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8);
|
||||
watch_display_character_lp_seconds('0' + date_time.unit.second % 10, 9);
|
||||
break;
|
||||
} else if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
|
||||
// everything before minutes is the same.
|
||||
pos = 6;
|
||||
sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second);
|
||||
} else {
|
||||
// other stuff changed; let's do it all.
|
||||
#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) {
|
||||
watch_clear_indicator(WATCH_INDICATOR_PM);
|
||||
} else {
|
||||
watch_set_indicator(WATCH_INDICATOR_PM);
|
||||
}
|
||||
date_time.unit.hour %= 12;
|
||||
if (date_time.unit.hour == 0) date_time.unit.hour = 12;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (settings->bit.clock_mode_24h && settings->bit.clock_24h_leading_zero && date_time.unit.hour < 10) {
|
||||
set_leading_zero = true;
|
||||
}
|
||||
|
||||
pos = 0;
|
||||
if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
|
||||
if (!watch_tick_animation_is_running()) watch_start_tick_animation(500);
|
||||
sprintf(buf, "%s%2d%2d%02d ", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute);
|
||||
} else {
|
||||
sprintf(buf, "%s%2d%2d%02d%02d", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second);
|
||||
}
|
||||
}
|
||||
watch_display_string(buf, pos);
|
||||
|
||||
if (set_leading_zero)
|
||||
watch_display_string("0", 4);
|
||||
|
||||
// handle alarm indicator
|
||||
if (state->alarm_enabled != settings->bit.alarm_enabled) _update_alarm_indicator(settings->bit.alarm_enabled, state);
|
||||
break;
|
||||
case EVENT_ALARM_LONG_PRESS:
|
||||
state->signal_enabled = !state->signal_enabled;
|
||||
if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL);
|
||||
else watch_clear_indicator(WATCH_INDICATOR_BELL);
|
||||
break;
|
||||
case EVENT_BACKGROUND_TASK:
|
||||
// uncomment this line to snap back to the clock face when the hour signal sounds:
|
||||
// movement_move_to_face(state->watch_face_index);
|
||||
movement_play_signal();
|
||||
break;
|
||||
default:
|
||||
return movement_default_loop_handler(event, settings);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void simple_clock_face_resign(movement_settings_t *settings, void *context) {
|
||||
(void) settings;
|
||||
(void) context;
|
||||
}
|
||||
|
||||
bool simple_clock_face_wants_background_task(movement_settings_t *settings, void *context) {
|
||||
(void) settings;
|
||||
simple_clock_state_t *state = (simple_clock_state_t *)context;
|
||||
if (!state->signal_enabled) return false;
|
||||
|
||||
watch_date_time date_time = watch_rtc_get_date_time();
|
||||
|
||||
return date_time.unit.minute == 0;
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Joey Castillo
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SIMPLE_CLOCK_FACE_H_
|
||||
#define SIMPLE_CLOCK_FACE_H_
|
||||
|
||||
/*
|
||||
* SIMPLE CLOCK FACE
|
||||
*
|
||||
* Displays the current time, matching the original operation of the watch.
|
||||
* This is the default display mode in most watch configurations.
|
||||
*
|
||||
* Long-press ALARM to toggle the hourly chime.
|
||||
*/
|
||||
|
||||
#include "movement.h"
|
||||
|
||||
typedef struct {
|
||||
uint32_t previous_date_time;
|
||||
uint8_t last_battery_check;
|
||||
uint8_t watch_face_index;
|
||||
bool signal_enabled;
|
||||
bool battery_low;
|
||||
bool alarm_enabled;
|
||||
} simple_clock_state_t;
|
||||
|
||||
void simple_clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||
void simple_clock_face_activate(movement_settings_t *settings, void *context);
|
||||
bool simple_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
||||
void simple_clock_face_resign(movement_settings_t *settings, void *context);
|
||||
bool simple_clock_face_wants_background_task(movement_settings_t *settings, void *context);
|
||||
|
||||
#define simple_clock_face ((const watch_face_t){ \
|
||||
simple_clock_face_setup, \
|
||||
simple_clock_face_activate, \
|
||||
simple_clock_face_loop, \
|
||||
simple_clock_face_resign, \
|
||||
simple_clock_face_wants_background_task, \
|
||||
})
|
||||
|
||||
#endif // SIMPLE_CLOCK_FACE_H_
|
||||
@@ -1,188 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Joey Castillo
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "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"};
|
||||
|
||||
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
|
||||
|
||||
switch (current_page) {
|
||||
case 0: // hour
|
||||
date_time.unit.hour = (date_time.unit.hour + 1) % 24;
|
||||
break;
|
||||
case 1: // minute
|
||||
date_time.unit.minute = (date_time.unit.minute + 1) % 60;
|
||||
break;
|
||||
case 2: // second
|
||||
date_time.unit.second = 0;
|
||||
break;
|
||||
case 3: // year
|
||||
date_time.unit.year = ((date_time.unit.year % 60) + 1);
|
||||
break;
|
||||
case 4: // month
|
||||
date_time.unit.month = (date_time.unit.month % 12) + 1;
|
||||
break;
|
||||
case 5: { // day
|
||||
date_time.unit.day = date_time.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.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);
|
||||
}
|
||||
|
||||
static void _abort_quick_ticks() {
|
||||
if (_quick_ticks_running) {
|
||||
_quick_ticks_running = false;
|
||||
movement_request_tick_frequency(4);
|
||||
}
|
||||
}
|
||||
|
||||
void set_time_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(uint8_t));
|
||||
}
|
||||
|
||||
void set_time_face_activate(movement_settings_t *settings, void *context) {
|
||||
(void) settings;
|
||||
*((uint8_t *)context) = 0;
|
||||
movement_request_tick_frequency(4);
|
||||
_quick_ticks_running = false;
|
||||
}
|
||||
|
||||
bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
|
||||
uint8_t current_page = *((uint8_t *)context);
|
||||
watch_date_time date_time = watch_rtc_get_date_time();
|
||||
|
||||
switch (event.event_type) {
|
||||
case EVENT_TICK:
|
||||
if (_quick_ticks_running) {
|
||||
if (watch_get_pin_level(BTN_ALARM)) _handle_alarm_button(settings, date_time, current_page);
|
||||
else _abort_quick_ticks();
|
||||
}
|
||||
break;
|
||||
case EVENT_ALARM_LONG_PRESS:
|
||||
if (current_page != 2) {
|
||||
_quick_ticks_running = true;
|
||||
movement_request_tick_frequency(8);
|
||||
}
|
||||
break;
|
||||
case EVENT_ALARM_LONG_UP:
|
||||
_abort_quick_ticks();
|
||||
break;
|
||||
case EVENT_MODE_BUTTON_UP:
|
||||
_abort_quick_ticks();
|
||||
movement_move_to_next_face();
|
||||
return false;
|
||||
case EVENT_LIGHT_BUTTON_DOWN:
|
||||
current_page = (current_page + 1) % SET_TIME_FACE_NUM_SETTINGS;
|
||||
*((uint8_t *)context) = current_page;
|
||||
break;
|
||||
case EVENT_ALARM_BUTTON_UP:
|
||||
_abort_quick_ticks();
|
||||
_handle_alarm_button(settings, date_time, current_page);
|
||||
break;
|
||||
case EVENT_TIMEOUT:
|
||||
_abort_quick_ticks();
|
||||
movement_move_to_face(0);
|
||||
break;
|
||||
default:
|
||||
return movement_default_loop_handler(event, settings);
|
||||
}
|
||||
|
||||
char buf[11];
|
||||
bool set_leading_zero = false;
|
||||
if (current_page < 3) {
|
||||
watch_set_colon();
|
||||
if (settings->bit.clock_mode_24h) {
|
||||
if (!settings->bit.clock_24h_leading_zero)
|
||||
watch_set_indicator(WATCH_INDICATOR_24H);
|
||||
else if (date_time.unit.hour < 10)
|
||||
set_leading_zero = true;
|
||||
sprintf(buf, "%s %2d%02d%02d", set_time_face_titles[current_page], date_time.unit.hour, date_time.unit.minute, date_time.unit.second);
|
||||
} else {
|
||||
sprintf(buf, "%s %2d%02d%02d", set_time_face_titles[current_page], (date_time.unit.hour % 12) ? (date_time.unit.hour % 12) : 12, date_time.unit.minute, date_time.unit.second);
|
||||
if (date_time.unit.hour < 12) watch_clear_indicator(WATCH_INDICATOR_PM);
|
||||
else watch_set_indicator(WATCH_INDICATOR_PM);
|
||||
}
|
||||
} else if (current_page < 6) {
|
||||
watch_clear_colon();
|
||||
watch_clear_indicator(WATCH_INDICATOR_24H);
|
||||
watch_clear_indicator(WATCH_INDICATOR_PM);
|
||||
sprintf(buf, "%s %2d%02d%02d", set_time_face_titles[current_page], date_time.unit.year + 20, date_time.unit.month, date_time.unit.day);
|
||||
} else {
|
||||
if (event.subsecond % 2) {
|
||||
watch_clear_colon();
|
||||
sprintf(buf, "%s ", set_time_face_titles[current_page]);
|
||||
} else {
|
||||
watch_set_colon();
|
||||
sprintf(buf, "%s %3d%02d ", set_time_face_titles[current_page], (int8_t) (movement_timezone_offsets[settings->bit.time_zone] / 60), (int8_t) (movement_timezone_offsets[settings->bit.time_zone] % 60) * (movement_timezone_offsets[settings->bit.time_zone] < 0 ? -1 : 1));
|
||||
}
|
||||
}
|
||||
|
||||
// blink up the parameter we're setting
|
||||
if (event.subsecond % 2 && !_quick_ticks_running) {
|
||||
switch (current_page) {
|
||||
case 0:
|
||||
case 3:
|
||||
buf[4] = buf[5] = ' ';
|
||||
break;
|
||||
case 1:
|
||||
case 4:
|
||||
buf[6] = buf[7] = ' ';
|
||||
break;
|
||||
case 2:
|
||||
case 5:
|
||||
buf[8] = buf[9] = ' ';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
watch_display_string(buf, 0);
|
||||
if (set_leading_zero)
|
||||
watch_display_string("0", 4);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_time_face_resign(movement_settings_t *settings, void *context) {
|
||||
(void) settings;
|
||||
(void) context;
|
||||
watch_set_led_off();
|
||||
watch_store_backup_data(settings->reg, 0);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Joey Castillo
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SET_TIME_FACE_H_
|
||||
#define SET_TIME_FACE_H_
|
||||
|
||||
/*
|
||||
* SET TIME face
|
||||
*
|
||||
* The default method for adjusting Sensor Watch time.
|
||||
*
|
||||
* The Time Set watch face allows you to set the time on Sensor Watch. Use
|
||||
* the LIGHT button to advance through the field you are setting, and the
|
||||
* ALARM button to change the value in that field. The fields are, in order:
|
||||
* Hour, Minute, Second, Year, Month, Day and Time Zone.
|
||||
*
|
||||
* For features like World Clock and Sunrise/Sunset to work correctly, you
|
||||
* must set the time to your local time, and the time zone to your local time
|
||||
* zone. This allows Sensor Watch to correctly offset the time. This also
|
||||
* means that when daylight savings time starts or ends, you must update
|
||||
* both the time and the time zone on this screen.
|
||||
*/
|
||||
|
||||
#include "movement.h"
|
||||
|
||||
void set_time_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||
void set_time_face_activate(movement_settings_t *settings, void *context);
|
||||
bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
||||
void set_time_face_resign(movement_settings_t *settings, void *context);
|
||||
|
||||
#define set_time_face ((const watch_face_t){ \
|
||||
set_time_face_setup, \
|
||||
set_time_face_activate, \
|
||||
set_time_face_loop, \
|
||||
set_time_face_resign, \
|
||||
NULL, \
|
||||
})
|
||||
|
||||
#endif // SET_TIME_FACE_H_
|
||||
Reference in New Issue
Block a user