Merge branch 'next'

This merge adds numerous individually small fixes and improvements
which amount to a sizeable set of features.

 - New hourly chime tunes added
 - Old hourly chime tunes improved
 - Display of characters on the stock LCD improved
 - Documentation improved
 - Simulator improved
 - Leap year handling improved
 - Months and their days sanity checked and fixed
 - More compile time configurable defaults added
 - Excessively exact time checks relaxed
 - Clock face indicators matched to original watch
 - Hardware interface issue fixed

The most significant new feature however is software debouncing.
The sensor watch now properly handles hardware switch bouncing,
making the button inputs much more precise at the cost of a small amount
of latency, greatly improving usability. Any watch faces which require
holding down buttons as part of their user interface, the pulsometer
for example, should see huge improvements in their usability.

 * 9c093f9 Merge PR #387 - configurable default birthdate/location
 * 879c48c Merge PR #417 - improve 24h only mode
 * db4097b Merge PR #426 - add temperature input to simulator
 * dea0566 Merge PR #428 - fix issues in sunrise/sunset
 * c8ca0d3 Merge PR #431 - fix wrong number of days in month
 * 95ca374 Merge PR #433 - fix clock face indicators
 * 663cd72 Merge PR #434 - fix leap years
 * a715265 Merge PR #437 - debouncing logic
 * c741332 Merge PR #439 - fix scheduled task misses
 * 657ff72 Merge PR #440 - fix countdown face issues
 * c8a87d3 Merge PR #441 - update documentation
 * dd04443 Merge PR #443 - improved t and y character display
 * 42dc151 Merge PR #447 - improve kim possible chime
 * fa0cdef Merge PR #450 - sync after enabling RTC
 * a67076f Merge PR #458 - add layla tune
 * 23c422b Merge PR #459 - add power rangers tune
 * a2e5417 Merge PR #461 - improve t/y special case docs

Tested-on-hardware-by: Alex Maestas <git@se30.xyz>
Tested-on-hardware-by: Matheus Afonso Martins Moreira <matheus@matheusmoreira.com>
Tested-on-hardware-by: Wesley Ellis <tahnok@gmail.com>
GitHub-Pull-Request: https://github.com/joeycastillo/Sensor-Watch/pull/460
This commit is contained in:
Matheus Afonso Martins Moreira 2024-09-03 01:03:08 -03:00
commit d4bd10ba5e
21 changed files with 287 additions and 100 deletions

View File

@ -2,6 +2,7 @@
#include <string.h>
#include <math.h>
#include "watch.h"
#include "watch_utility.h"
const int8_t UTC_OFFSET = 4; // set to your current UTC offset to see correct beats time
const uint8_t BEAT_REFRESH_FREQUENCY = 8;
@ -203,7 +204,6 @@ void set_time_mode_handle_primary_button(void) {
void set_time_mode_handle_secondary_button(void) {
watch_date_time date_time = watch_rtc_get_date_time();
const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31};
switch (application_state.page) {
case 0: // hour
@ -224,13 +224,10 @@ void set_time_mode_handle_secondary_button(void) {
break;
case 5: // day
date_time.unit.day = date_time.unit.day + 1;
// can't set to the 29th on a leap year. if it's february 29, set to 11:59 on the 28th.
// and it should roll over.
if (date_time.unit.day > days_in_month[date_time.unit.month - 1]) {
date_time.unit.day = 1;
}
break;
}
if (date_time.unit.day > days_in_month(date_time.unit.month, date_time.unit.year + WATCH_RTC_REFERENCE_YEAR))
date_time.unit.day = 1;
watch_rtc_set_date_time(date_time);
}

View File

@ -23,6 +23,16 @@
*/
#define MOVEMENT_LONG_PRESS_TICKS 64
#define DEBOUNCE_TICKS_DOWN 0
#define DEBOUNCE_TICKS_UP 0
/*
DEBOUNCE_TICKS_DOWN and DEBOUNCE_TICKS_UP are in terms of fast_cb ticks after a button is pressed.
The logic is that pressed of a button are ignored until the cb_fast_tick function runs this variable amount of times.
Without modifying the code, the cb_fast_tick frequency is 128Hz, or 7.8125ms.
It is not suggested to set this value to one for debouncing, as the callback occurs asynchronously of the button's press,
meaning that if a button was pressed and 7ms passed since th elast time cb_fast_tick was called, then there will be only 812.5us
of debounce time.
*/
#include <stdio.h>
#include <string.h>
@ -95,6 +105,31 @@
#define MOVEMENT_DEFAULT_LED_DURATION 1
#endif
// Default to no set location latitude
#ifndef MOVEMENT_DEFAULT_LATITUDE
#define MOVEMENT_DEFAULT_LATITUDE 0
#endif
// Default to no set location longitude
#ifndef MOVEMENT_DEFAULT_LONGITUDE
#define MOVEMENT_DEFAULT_LONGITUDE 0
#endif
// Default to no set birthdate year
#ifndef MOVEMENT_DEFAULT_BIRTHDATE_YEAR
#define MOVEMENT_DEFAULT_BIRTHDATE_YEAR 0
#endif
// Default to no set birthdate month
#ifndef MOVEMENT_DEFAULT_BIRTHDATE_MONTH
#define MOVEMENT_DEFAULT_BIRTHDATE_MONTH 0
#endif
// Default to no set birthdate day
#ifndef MOVEMENT_DEFAULT_BIRTHDATE_DAY
#define MOVEMENT_DEFAULT_BIRTHDATE_DAY 0
#endif
#if __EMSCRIPTEN__
#include <emscripten.h>
#endif
@ -169,6 +204,9 @@ static inline void _movement_reset_inactivity_countdown(void) {
static inline void _movement_enable_fast_tick_if_needed(void) {
if (!movement_state.fast_tick_enabled) {
movement_state.fast_ticks = 0;
movement_state.debounce_ticks_light = 0;
movement_state.debounce_ticks_alarm = 0;
movement_state.debounce_ticks_mode = 0;
watch_rtc_register_periodic_callback(cb_fast_tick, 128);
movement_state.fast_tick_enabled = true;
}
@ -177,6 +215,7 @@ static inline void _movement_enable_fast_tick_if_needed(void) {
static inline void _movement_disable_fast_tick_if_possible(void) {
if ((movement_state.light_ticks == -1) &&
(movement_state.alarm_ticks == -1) &&
((movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm) == 0) &&
((movement_state.light_down_timestamp + movement_state.mode_down_timestamp + movement_state.alarm_down_timestamp) == 0)) {
movement_state.fast_tick_enabled = false;
watch_rtc_disable_periodic_callback(128);
@ -201,7 +240,7 @@ static void _movement_handle_scheduled_tasks(void) {
for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) {
if (scheduled_tasks[i].reg) {
if (scheduled_tasks[i].reg == date_time.reg) {
if (scheduled_tasks[i].reg <= date_time.reg) {
scheduled_tasks[i].reg = 0;
movement_event_t background_event = { EVENT_BACKGROUND_TASK, 0 };
watch_faces[i].loop(background_event, &movement_state.settings, watch_face_contexts[i]);
@ -328,6 +367,14 @@ static void end_buzzing_and_disable_buzzer(void) {
watch_disable_buzzer();
}
static void set_initial_clock_mode(void) {
#ifdef CLOCK_FACE_24H_ONLY
movement_state.settings.bit.clock_mode_24h = true;
#else
movement_state.settings.bit.clock_mode_24h = MOVEMENT_DEFAULT_24H_MODE;
#endif
}
void movement_play_signal(void) {
void *maybe_disable_buzzer = end_buzzing_and_disable_buzzer;
if (watch_is_buzzer_or_led_enabled()) {
@ -376,14 +423,18 @@ void app_init(void) {
#endif
memset(&movement_state, 0, sizeof(movement_state));
movement_state.settings.bit.clock_mode_24h = MOVEMENT_DEFAULT_24H_MODE;
set_initial_clock_mode();
movement_state.settings.bit.led_red_color = MOVEMENT_DEFAULT_RED_COLOR;
movement_state.settings.bit.led_green_color = MOVEMENT_DEFAULT_GREEN_COLOR;
movement_state.settings.bit.button_should_sound = MOVEMENT_DEFAULT_BUTTON_SOUND;
movement_state.settings.bit.to_interval = MOVEMENT_DEFAULT_TIMEOUT_INTERVAL;
movement_state.settings.bit.le_interval = MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL;
movement_state.settings.bit.led_duration = MOVEMENT_DEFAULT_LED_DURATION;
movement_state.location.bit.latitude = MOVEMENT_DEFAULT_LATITUDE;
movement_state.location.bit.longitude = MOVEMENT_DEFAULT_LONGITUDE;
movement_state.birthdate.bit.year = MOVEMENT_DEFAULT_BIRTHDATE_YEAR;
movement_state.birthdate.bit.month = MOVEMENT_DEFAULT_BIRTHDATE_MONTH;
movement_state.birthdate.bit.day = MOVEMENT_DEFAULT_BIRTHDATE_DAY;
movement_state.light_ticks = -1;
movement_state.alarm_ticks = -1;
movement_state.next_available_backup_register = 4;
@ -406,10 +457,14 @@ void app_init(void) {
void app_wake_from_backup(void) {
movement_state.settings.reg = watch_get_backup_data(0);
movement_state.location.reg = watch_get_backup_data(1);
movement_state.birthdate.reg = watch_get_backup_data(2);
}
void app_setup(void) {
watch_store_backup_data(movement_state.settings.reg, 0);
watch_store_backup_data(movement_state.location.reg, 1);
watch_store_backup_data(movement_state.birthdate.reg, 2);
static bool is_first_launch = true;
@ -462,6 +517,7 @@ void app_wake_from_standby(void) {
static void _sleep_mode_app_loop(void) {
movement_state.needs_wake = false;
movement_state.ignore_alarm_btn_after_sleep = true;
// as long as le_mode_ticks is -1 (i.e. we are in low energy mode), we wake up here, update the screen, and go right back to sleep.
while (movement_state.le_mode_ticks == -1) {
// we also have to handle background tasks here in the mini-runloop
@ -627,29 +683,66 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e
// now that that's out of the way, handle falling edge
uint16_t diff = movement_state.fast_ticks - *down_timestamp;
*down_timestamp = 0;
_movement_disable_fast_tick_if_possible();
// any press over a half second is considered a long press. Fire the long-up event
if (diff > MOVEMENT_LONG_PRESS_TICKS) return button_down_event_type + 3;
else return button_down_event_type + 1;
}
}
void cb_light_btn_interrupt(void) {
bool pin_level = watch_get_pin_level(BTN_LIGHT);
static movement_event_type_t btn_action(bool pin_level, int code, uint16_t *timestamp) {
_movement_reset_inactivity_countdown();
event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp);
return _figure_out_button_event(pin_level, code, timestamp);
}
static void light_btn_action(bool pin_level) {
event.event_type = btn_action(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp);
}
static void mode_btn_action(bool pin_level) {
event.event_type = btn_action(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp);
}
static void alarm_btn_action(bool pin_level) {
uint8_t event_type = btn_action(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp);
if (movement_state.ignore_alarm_btn_after_sleep){
if (event_type == EVENT_ALARM_BUTTON_UP || event_type == EVENT_ALARM_LONG_UP) movement_state.ignore_alarm_btn_after_sleep = false;
return;
}
event.event_type = event_type;
}
static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, uint16_t *down_timestamp, void (*function)(bool)) {
if (*debounce_ticks == 0) {
bool pin_level = watch_get_pin_level(pin);
function(pin_level);
*debounce_ticks = pin_level ? DEBOUNCE_TICKS_DOWN : DEBOUNCE_TICKS_UP;
if (*debounce_ticks != 0) _movement_enable_fast_tick_if_needed();
}
else
*down_timestamp = 0;
}
static void disable_if_needed(uint8_t *ticks) {
if (*ticks > 0 && --*ticks == 0)
_movement_disable_fast_tick_if_possible();
}
static void movement_disable_if_debounce_complete(void) {
disable_if_needed(&movement_state.debounce_ticks_light);
disable_if_needed(&movement_state.debounce_ticks_alarm);
disable_if_needed(&movement_state.debounce_ticks_mode);
}
void cb_light_btn_interrupt(void) {
debounce_btn_press(BTN_LIGHT, &movement_state.debounce_ticks_light, &movement_state.light_down_timestamp, light_btn_action);
}
void cb_mode_btn_interrupt(void) {
bool pin_level = watch_get_pin_level(BTN_MODE);
_movement_reset_inactivity_countdown();
event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp);
debounce_btn_press(BTN_MODE, &movement_state.debounce_ticks_mode, &movement_state.mode_down_timestamp, mode_btn_action);
}
void cb_alarm_btn_interrupt(void) {
bool pin_level = watch_get_pin_level(BTN_ALARM);
_movement_reset_inactivity_countdown();
event.event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp);
debounce_btn_press(BTN_ALARM, &movement_state.debounce_ticks_alarm, &movement_state.alarm_down_timestamp, alarm_btn_action);
}
void cb_alarm_btn_extwake(void) {
@ -662,6 +755,8 @@ void cb_alarm_fired(void) {
}
void cb_fast_tick(void) {
movement_disable_if_debounce_complete();
if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0)
movement_state.fast_ticks++;
if (movement_state.light_ticks > 0) movement_state.light_ticks--;
if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--;

View File

@ -242,6 +242,8 @@ typedef struct {
typedef struct {
// properties stored in BACKUP register
movement_settings_t settings;
movement_location_t location;
movement_birthdate_t birthdate;
// transient properties
int16_t current_face_idx;
@ -270,6 +272,10 @@ typedef struct {
// low energy mode countdown
int32_t le_mode_ticks;
uint8_t debounce_ticks_light;
uint8_t debounce_ticks_alarm;
uint8_t debounce_ticks_mode;
bool ignore_alarm_btn_after_sleep;
// app resignation countdown (TODO: consolidate with LE countdown?)
int16_t timeout_ticks;

View File

@ -76,15 +76,15 @@ const watch_face_t watch_faces[] = {
/* Set the timeout before switching to low energy mode
* Valid values are:
* 0: Never
* 1: 1 hour
* 2: 2 hours
* 3: 6 hours
* 4: 12 hours
* 5: 1 day
* 6: 2 days
* 1: 10 mins
* 2: 1 hour
* 3: 2 hours
* 4: 6 hours
* 5: 12 hours
* 6: 1 day
* 7: 7 days
*/
#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 1
#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 2
/* Set the led duration
* Valid values are:
@ -95,4 +95,20 @@ const watch_face_t watch_faces[] = {
*/
#define MOVEMENT_DEFAULT_LED_DURATION 1
/* The latitude and longitude used for the wearers location
* Set signed values in 1/100ths of a degree
*/
#define MOVEMENT_DEFAULT_LATITUDE 0
#define MOVEMENT_DEFAULT_LONGITUDE 0
/* The wearers birthdate
* Valid values:
* Year: 1 - 4095
* Month: 1 - 12
* Day: 1 - 31
*/
#define MOVEMENT_DEFAULT_BIRTHDATE_YEAR 0
#define MOVEMENT_DEFAULT_BIRTHDATE_MONTH 0
#define MOVEMENT_DEFAULT_BIRTHDATE_DAY 0
#endif // MOVEMENT_CONFIG_H_

View File

@ -70,18 +70,53 @@ int8_t signal_tune[] = {
#ifdef SIGNAL_TUNE_KIM_POSSIBLE
int8_t signal_tune[] = {
BUZZER_NOTE_G7, 6,
BUZZER_NOTE_REST, 1,
BUZZER_NOTE_G4, 3,
BUZZER_NOTE_G4, 2,
BUZZER_NOTE_REST, 5,
BUZZER_NOTE_G7, 6,
BUZZER_NOTE_REST, 1,
BUZZER_NOTE_G4, 3,
BUZZER_NOTE_G4, 2,
BUZZER_NOTE_REST, 5,
BUZZER_NOTE_A7SHARP_B7FLAT, 6,
BUZZER_NOTE_REST, 2,
BUZZER_NOTE_G7, 6,
BUZZER_NOTE_G4, 2,
0
};
#endif // SIGNAL_TUNE_KIM_POSSIBLE
#ifdef SIGNAL_TUNE_POWER_RANGERS
int8_t signal_tune[] = {
BUZZER_NOTE_D8, 6,
BUZZER_NOTE_REST, 8,
BUZZER_NOTE_D8, 6,
BUZZER_NOTE_REST, 8,
BUZZER_NOTE_C8, 6,
BUZZER_NOTE_REST, 2,
BUZZER_NOTE_D8, 6,
BUZZER_NOTE_REST, 8,
BUZZER_NOTE_F8, 6,
BUZZER_NOTE_REST, 8,
BUZZER_NOTE_D8, 6,
0
};
#endif // SIGNAL_TUNE_POWER_RANGERS
#ifdef SIGNAL_TUNE_LAYLA
int8_t signal_tune[] = {
BUZZER_NOTE_A6, 5,
BUZZER_NOTE_REST, 1,
BUZZER_NOTE_C7, 5,
BUZZER_NOTE_REST, 1,
BUZZER_NOTE_D7, 5,
BUZZER_NOTE_REST, 1,
BUZZER_NOTE_F7, 5,
BUZZER_NOTE_REST, 1,
BUZZER_NOTE_D7, 5,
BUZZER_NOTE_REST, 1,
BUZZER_NOTE_C7, 5,
BUZZER_NOTE_REST, 1,
BUZZER_NOTE_D7, 20,
0
};
#endif // SIGNAL_TUNE_LAYLA
#endif // MOVEMENT_CUSTOM_SIGNAL_TUNES_H_

View File

@ -42,10 +42,6 @@
#define CLOCK_FACE_LOW_BATTERY_VOLTAGE_THRESHOLD 2200
#endif
#ifndef CLOCK_FACE_24H_ONLY
#define CLOCK_FACE_24H_ONLY 0
#endif
typedef struct {
struct {
watch_date_time previous;
@ -57,8 +53,11 @@ typedef struct {
} clock_state_t;
static bool clock_is_in_24h_mode(movement_settings_t *settings) {
if (CLOCK_FACE_24H_ONLY) { return true; }
#ifdef CLOCK_FACE_24H_ONLY
return true;
#else
return settings->bit.clock_mode_24h;
#endif
}
static void clock_indicate(WatchIndicatorSegment indicator, bool on) {
@ -70,11 +69,11 @@ static void clock_indicate(WatchIndicatorSegment indicator, bool on) {
}
static void clock_indicate_alarm(movement_settings_t *settings) {
clock_indicate(WATCH_INDICATOR_BELL, settings->bit.alarm_enabled);
clock_indicate(WATCH_INDICATOR_SIGNAL, settings->bit.alarm_enabled);
}
static void clock_indicate_time_signal(clock_state_t *clock) {
clock_indicate(WATCH_INDICATOR_SIGNAL, clock->time_signal_enabled);
clock_indicate(WATCH_INDICATOR_BELL, clock->time_signal_enabled);
}
static void clock_indicate_24h(movement_settings_t *settings) {

View File

@ -51,7 +51,11 @@ void simple_clock_face_activate(movement_settings_t *settings, void *context) {
if (watch_tick_animation_is_running()) watch_stop_tick_animation();
#ifdef CLOCK_FACE_24H_ONLY
watch_set_indicator(WATCH_INDICATOR_24H);
#else
if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H);
#endif
// handle chime indicator
if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL);
@ -106,6 +110,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting
sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second);
} else {
// other stuff changed; let's do it all.
#ifndef CLOCK_FACE_24H_ONLY
if (!settings->bit.clock_mode_24h) {
// if we are in 12 hour mode, do some cleanup.
if (date_time.unit.hour < 12) {
@ -116,6 +121,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting
date_time.unit.hour %= 12;
if (date_time.unit.hour == 0) date_time.unit.hour = 12;
}
#endif
pos = 0;
if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
if (!watch_tick_animation_is_running()) watch_start_tick_animation(500);

View File

@ -87,6 +87,9 @@ static void draw(countdown_state_t *state, uint8_t subsecond) {
switch (state->mode) {
case cd_running:
if (state->target_ts <= state->now_ts)
delta = 0;
else
delta = state->target_ts - state->now_ts;
result = div(delta, 60);
state->seconds = result.rem;
@ -97,6 +100,7 @@ static void draw(countdown_state_t *state, uint8_t subsecond) {
break;
case cd_reset:
case cd_paused:
watch_clear_indicator(WATCH_INDICATOR_BELL);
sprintf(buf, "CD %2d%02d%02d", state->hours, state->minutes, state->seconds);
break;
case cd_setting:
@ -130,7 +134,6 @@ static void pause(countdown_state_t *state) {
static void reset(countdown_state_t *state) {
state->mode = cd_reset;
movement_cancel_background_task();
watch_clear_indicator(WATCH_INDICATOR_BELL);
load_countdown(state);
}

View File

@ -26,8 +26,7 @@
#include <string.h>
#include "day_one_face.h"
#include "watch.h"
static const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31};
#include "watch_utility.h"
static uint32_t _day_one_face_juliandaynum(uint16_t year, uint16_t month, uint16_t day) {
// from here: https://en.wikipedia.org/wiki/Julian_day#Julian_day_number_calculation
@ -66,13 +65,12 @@ static void _day_one_face_increment(day_one_state_t *state) {
break;
case PAGE_DAY:
state->birth_day = state->birth_day + 1;
if (state->birth_day == 0 || state->birth_day > days_in_month[state->birth_month - 1]) {
state->birth_day = 1;
}
break;
default:
break;
}
if (state->birth_day == 0 || state->birth_day > days_in_month(state->birth_month, state->birth_year))
state->birth_day = 1;
}
void day_one_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {

View File

@ -49,7 +49,7 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s
double rise, set, minutes, seconds;
bool show_next_match = false;
movement_location_t movement_location;
if (state->longLatToUse == 0)
if (state->longLatToUse == 0 || _location_count <= 1)
movement_location = (movement_location_t) watch_get_backup_data(1);
else{
movement_location.bit.latitude = longLatPresets[state->longLatToUse].latitude;
@ -359,7 +359,7 @@ bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *setti
_sunrise_sunset_face_update_location_register(state);
}
_sunrise_sunset_face_update_settings_display(event, context);
} else if (_location_count == 1) {
} else if (_location_count <= 1) {
movement_illuminate_led();
}
if (state->page == 0) {
@ -368,7 +368,7 @@ bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *setti
}
break;
case EVENT_LIGHT_LONG_PRESS:
if (_location_count == 1) break;
if (_location_count <= 1) break;
else if (!state->page) movement_illuminate_led();
break;
case EVENT_LIGHT_BUTTON_UP:

View File

@ -27,6 +27,7 @@
#include "time_left_face.h"
#include "watch.h"
#include "watch_private_display.h"
#include "watch_utility.h"
const char _state_titles[][3] = {{'D', 'L', ' '}, {'D', 'L', ' '}, {'D', 'A', ' '}, {'D', 'A', ' '}, {'Y', 'R', 'b'}, {'M', 'O', 'b'}, {'D', 'A', 'b'},
{'Y', 'R', 'd'}, {'M', 'O', 'd'}, {'D', 'A', 'd'}};
@ -158,8 +159,6 @@ static void _draw(time_left_state_t *state, uint8_t subsecond) {
/// @brief handle short or long pressing the alarm button
static void _handle_alarm_button(time_left_state_t *state) {
const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31};
uint32_t tmp_day;
switch (state->current_page) {
case TIME_LEFT_FACE_SETTINGS_STATE: // birth year
state->birth_date.bit.year++;
@ -169,14 +168,7 @@ static void _handle_alarm_button(time_left_state_t *state) {
state->birth_date.bit.month = (state->birth_date.bit.month % 12) + 1;
break;
case TIME_LEFT_FACE_SETTINGS_STATE + 2: // birth day
tmp_day = state->birth_date.bit.day; // use a temporary variable to avoid messing up the months
tmp_day++;
// handle February 29th on a leap year
if (((tmp_day > days_in_month[state->birth_date.bit.month - 1]) && (state->birth_date.bit.month != 2 || (state->birth_date.bit.year % 4) != 0))
|| (state->birth_date.bit.month == 2 && (state->birth_date.bit.year % 4) == 0 && tmp_day > 29)) {
tmp_day = 1;
}
state->birth_date.bit.day = tmp_day;
state->birth_date.bit.day++;
break;
case TIME_LEFT_FACE_SETTINGS_STATE + 3: // target year
state->target_date.bit.year++;
@ -186,16 +178,13 @@ static void _handle_alarm_button(time_left_state_t *state) {
state->target_date.bit.month = (state->target_date.bit.month % 12) + 1;
break;
case TIME_LEFT_FACE_SETTINGS_STATE + 5: // target day
tmp_day = state->target_date.bit.day;
tmp_day++;
// handle February 29th on a leap year
if (((tmp_day > days_in_month[state->target_date.bit.month - 1]) && (state->target_date.bit.month != 2 || (state->target_date.bit.year % 4) != 0))
|| (state->target_date.bit.month == 2 && (state->target_date.bit.year % 4) == 0 && tmp_day > 29)) {
tmp_day = 1;
}
state->target_date.bit.day = tmp_day;
state->target_date.bit.day++;
break;
}
if (state->birth_date.bit.day > days_in_month(state->birth_date.bit.month, state->birth_date.bit.year))
state->birth_date.bit.day = 1;
if (state->target_date.bit.day > days_in_month(state->target_date.bit.month, state->birth_date.bit.year))
state->target_date.bit.day = 1;
}
static void _initiate_setting(time_left_state_t *state) {

View File

@ -48,15 +48,19 @@
* o SHA512
*
* Instructions:
* o Find your secret key(s) and convert them to the required format.
* o Use https://cryptii.com/pipes/base32-to-hex to convert base32 to hex
* o Use https://github.com/susam/mintotp to generate test codes for verification
* o Edit global variables in "totp_face.c" to configure your stored keys:
* o "keys", "key_sizes", "timesteps", and "algorithms" set the
* cryptographic parameters for each secret key.
* o "labels" sets the two-letter label for each key
* (This replaces the day-of-week indicator)
* o Once finished, remove the two provided examples.
* o Find your secret key(s).
* o Use https://github.com/susam/mintotp to generate test codes for
* verification
* o Edit global `credentials` variable in "totp_face.c" to configure your
* TOTP credentials. The file includes two examples that you can use as a
* reference. Credentials are added with the `CREDENTIAL` macro in the form
* `CREDENTIAL(label, key, algorithm, timestep)` where:
* o `label` is a 2 character label that is displayed in the weekday digits
* to identify the TOTP credential.
* o `key` is a string with the base32 encoded secret.
* o `algorithm` is one of the supported hashing algorithms listed above.
* o `timestep` is how often the TOTP refreshes in seconds. This is usually
* 30 seconds.
*
* If you have more than one secret key, press ALARM to cycle through them.
* Press LIGHT to cycle in the other direction or keep it pressed longer to

View File

@ -26,8 +26,8 @@
#include "preferences_face.h"
#include "watch.h"
#define PREFERENCES_FACE_NUM_PREFEFENCES (7)
const char preferences_face_titles[PREFERENCES_FACE_NUM_PREFEFENCES][11] = {
#define PREFERENCES_FACE_NUM_PREFERENCES (7)
const char preferences_face_titles[PREFERENCES_FACE_NUM_PREFERENCES][11] = {
"CL ", // Clock: 12 or 24 hour
"BT Beep ", // Buttons: should they beep?
"TO ", // Timeout: how long before we snap back to the clock face?
@ -65,7 +65,7 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings
movement_move_to_next_face();
return false;
case EVENT_LIGHT_BUTTON_DOWN:
current_page = (current_page + 1) % PREFERENCES_FACE_NUM_PREFEFENCES;
current_page = (current_page + 1) % PREFERENCES_FACE_NUM_PREFERENCES;
*((uint8_t *)context) = current_page;
break;
case EVENT_ALARM_BUTTON_UP:
@ -99,7 +99,9 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings
default:
return movement_default_loop_handler(event, settings);
}
#ifdef CLOCK_FACE_24H_ONLY
if (current_page == 0) current_page++; // Skips past 12/24HR mode
#endif
watch_display_string((char *)preferences_face_titles[current_page], 0);
// blink active setting on even-numbered quarter-seconds

View File

@ -25,6 +25,7 @@
#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"};
@ -33,7 +34,6 @@ static bool _quick_ticks_running;
static void _handle_alarm_button(movement_settings_t *settings, watch_date_time date_time, uint8_t current_page) {
// handles short or long pressing of the alarm button
const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
switch (current_page) {
case 0: // hour
@ -52,14 +52,7 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time
date_time.unit.month = (date_time.unit.month % 12) + 1;
break;
case 5: { // day
uint32_t tmp_day = date_time.unit.day; // use a temporary variable to avoid messing up the months
tmp_day = tmp_day + 1;
// handle February 29th on a leap year
if (((tmp_day > days_in_month[date_time.unit.month - 1]) && (date_time.unit.month != 2 || (date_time.unit.year % 4) != 0))
|| (date_time.unit.month == 2 && (date_time.unit.year % 4) == 0 && tmp_day > 29)) {
tmp_day = 1;
}
date_time.unit.day = tmp_day;
date_time.unit.day = date_time.unit.day + 1;
break;
}
case 6: // time zone
@ -67,6 +60,8 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time
if (settings->bit.time_zone > 40) settings->bit.time_zone = 0;
break;
}
if (date_time.unit.day > days_in_month(date_time.unit.month, date_time.unit.year + WATCH_RTC_REFERENCE_YEAR))
date_time.unit.day = 1;
watch_rtc_set_date_time(date_time);
}

View File

@ -26,6 +26,7 @@
#include <stdlib.h>
#include "set_time_hackwatch_face.h"
#include "watch.h"
#include "watch_utility.h"
char set_time_hackwatch_face_titles[][3] = {"HR", "M1", "SE", "YR", "MO", "DA", "ZO"};
#define set_time_hackwatch_face_NUM_SETTINGS (sizeof(set_time_hackwatch_face_titles) / sizeof(*set_time_hackwatch_face_titles))
@ -47,7 +48,6 @@ void set_time_hackwatch_face_activate(movement_settings_t *settings, void *conte
bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
uint8_t current_page = *((uint8_t *)context);
const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31};
if (event.subsecond == 15) // Delay displayed time update by ~0.5 seconds, to align phase exactly to main clock at 1Hz
date_time_settings = watch_rtc_get_date_time();
@ -119,10 +119,8 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s
break;
case 5: // day
date_time_settings.unit.day = date_time_settings.unit.day - 2;
// can't set to the 29th on a leap year. if it's february 29, set to 11:59 on the 28th.
// and it should roll over.
if (date_time_settings.unit.day == 0) {
date_time_settings.unit.day = days_in_month[date_time_settings.unit.month - 1];
date_time_settings.unit.day = days_in_month(date_time_settings.unit.month, date_time_settings.unit.year + WATCH_RTC_REFERENCE_YEAR);
} else
date_time_settings.unit.day++;
break;
@ -167,17 +165,14 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s
break;
case 5: // day
date_time_settings.unit.day = date_time_settings.unit.day + 1;
// can't set to the 29th on a leap year. if it's february 29, set to 11:59 on the 28th.
// and it should roll over.
if (date_time_settings.unit.day > days_in_month[date_time_settings.unit.month - 1]) {
date_time_settings.unit.day = 1;
}
break;
case 6: // time zone
settings->bit.time_zone++;
if (settings->bit.time_zone > 40) settings->bit.time_zone = 0;
break;
}
if (date_time_settings.unit.day > days_in_month(date_time_settings.unit.month, date_time_settings.unit.year + WATCH_RTC_REFERENCE_YEAR))
date_time_settings.unit.day = 1;
if (current_page != 2) // Do not set time when we are at seconds, it was already set previously
watch_rtc_set_date_time(date_time_settings);
//TODO: Do not update whole RTC, just what we are changing

View File

@ -77,6 +77,7 @@ void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback, bool le
RTC->MODE2.TAMPCTRL.reg = config;
// re-enable the RTC
RTC->MODE2.CTRLA.bit.ENABLE = 1;
while (RTC->MODE2.SYNCBUSY.bit.ENABLE); // wait for RTC to be enabled
NVIC_ClearPendingIRQ(RTC_IRQn);
NVIC_EnableIRQ(RTC_IRQn);

View File

@ -45,7 +45,15 @@ void thermistor_driver_disable(void) {
// Disable the enable pin's output circuitry.
watch_disable_digital_output(THERMISTOR_ENABLE_PIN);
}
#if __EMSCRIPTEN__
#include <emscripten.h>
float thermistor_driver_get_temperature(void)
{
return EM_ASM_DOUBLE({
return temp_c || 25.0;
});
}
#else
float thermistor_driver_get_temperature(void) {
// set the enable pin to the level that powers the thermistor circuit.
watch_set_pin_level(THERMISTOR_ENABLE_PIN, THERMISTOR_ENABLE_VALUE);
@ -56,3 +64,4 @@ float thermistor_driver_get_temperature(void) {
return watch_utility_thermistor_temperature(value, THERMISTOR_HIGH_SIDE, THERMISTOR_B_COEFFICIENT, THERMISTOR_NOMINAL_TEMPERATURE, THERMISTOR_NOMINAL_RESISTANCE, THERMISTOR_SERIES_RESISTANCE);
}
#endif

View File

@ -43,6 +43,8 @@ void watch_display_character(uint8_t character, uint8_t position) {
else if (character == 'M' || character == 'm' || character == 'N') character = 'n'; // M and uppercase N need to be lowercase n
else if (character == 'c') character = 'C'; // C needs to be uppercase
else if (character == 'J') character = 'j'; // same
else if (character == 't' || character == 'T') character = '+'; // t in those locations looks like E otherwise
else if (character == 'y' || character == 'Y') character = '4'; // y in those locations looks like g otherwise
else if (character == 'v' || character == 'V' || character == 'U' || character == 'W' || character == 'w') character = 'u'; // bottom segment duplicated, so show in top half
} else {
if (character == 'u') character = 'v'; // we can use the bottom segment; move to lower half

View File

@ -315,3 +315,11 @@ uint32_t watch_utility_offset_timestamp(uint32_t now, int8_t hours, int8_t minut
new += seconds;
return new;
}
uint8_t days_in_month(uint8_t month, uint16_t year) {
static const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
uint8_t days = days_in_month[month - 1];
if (month == 2 && is_leap(year))
days += 1;
return days;
}

View File

@ -164,4 +164,10 @@ float watch_utility_thermistor_temperature(uint16_t value, bool highside, float
*/
uint32_t watch_utility_offset_timestamp(uint32_t now, int8_t hours, int8_t minutes, int8_t seconds);
/** @brief Returns the number of days in a month. It also handles Leap Years for February.
* @param month The month of the date (1-12)
* @param year The year of the date (ex. 2022)
*/
uint8_t days_in_month(uint8_t month, uint16_t year);
#endif

View File

@ -905,6 +905,11 @@
<div>
<button onclick="getLocation()">Set register (will prompt for access)</button>
</div>
<h2>Temp.</h2>
<div>
<input type="number" min="-100" max="120" id="temp-c" />C
<button onclick="setTemp()">Set</button>
</div>
</div>
<form onSubmit="sendText(); return false" style="display: flex; flex-direction: column; width: 100%">
@ -962,6 +967,7 @@
lat = 0;
lon = 0;
tx = "";
temp_c = 25.0;
function updateLocation(location) {
lat = Math.round(location.coords.latitude * 100);
lon = Math.round(location.coords.longitude * 100);
@ -1038,10 +1044,25 @@
document.getElementById(skin).checked = true;
setSkin(skin);
}
function setTemp() {
let tempInput = document.getElementById("temp-c");
if (!tempInput) {
return console.warn("no input found");
}
if (tempInput.value == "") {
return console.warn("no value in input");
}
try {
temp_c = Number.parseFloat(tempInput.value);
} catch (e) {
return console.warn("input value is not a valid float:", tempInput.value, e);
}
}
loadPrefs();
</script>
{{{ SCRIPT }}}
</body>
</html>