movement: more granular button and LED timing via fast tick
This commit is contained in:
parent
812c6c2612
commit
03fb09f5b4
@ -51,7 +51,7 @@ This function is called just before your watch enters the foreground. If your wa
|
|||||||
|
|
||||||
### watch_face_loop
|
### watch_face_loop
|
||||||
|
|
||||||
This is a lot like your loop() function in Arduinoland in that it is called repeatedly whenever your watch face is on screen. There is one crucial difference though: it is called less often. By default, this function is called once per second, and in response to events like button presses. You can request a more frequent tick interval by calling `movement_request_tick_frequency` with any power of 2 from 1 to 128.
|
This is a lot like your loop() function in Arduinoland in that it is called repeatedly whenever your watch face is on screen. There is one crucial difference though: it is called less often. By default, this function is called once per second, and in response to events like button presses. You can request a more frequent tick interval by calling `movement_request_tick_frequency` with any power of 2 from 1 to 64. (there is a 128 Hz prescaler tick, but Movement reserves that for its own use)
|
||||||
|
|
||||||
In addition to the settings and context, this function receives another parameter: an `event`. This is a struct containing information about the event that triggered the update. You mostly need to check the `event_type` to determine what kind of event triggered the loop. A detailed list of all events is provided at the bottom of this document.
|
In addition to the settings and context, this function receives another parameter: an `event`. This is a struct containing information about the event that triggered the update. You mostly need to check the `event_type` to determine what kind of event triggered the loop. A detailed list of all events is provided at the bottom of this document.
|
||||||
|
|
||||||
|
@ -63,6 +63,7 @@ void cb_light_btn_interrupt();
|
|||||||
void cb_alarm_btn_interrupt();
|
void cb_alarm_btn_interrupt();
|
||||||
void cb_alarm_btn_extwake();
|
void cb_alarm_btn_extwake();
|
||||||
void cb_alarm_fired();
|
void cb_alarm_fired();
|
||||||
|
void cb_fast_tick();
|
||||||
void cb_tick();
|
void cb_tick();
|
||||||
|
|
||||||
static inline void _movement_reset_inactivity_countdown() {
|
static inline void _movement_reset_inactivity_countdown() {
|
||||||
@ -70,6 +71,20 @@ static inline void _movement_reset_inactivity_countdown() {
|
|||||||
movement_state.timeout_ticks = movement_timeout_inactivity_deadlines[movement_state.settings.bit.to_interval];
|
movement_state.timeout_ticks = movement_timeout_inactivity_deadlines[movement_state.settings.bit.to_interval];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void _movement_enable_fast_tick_if_needed() {
|
||||||
|
if (!movement_state.fast_tick_enabled) {
|
||||||
|
movement_state.fast_ticks = 0;
|
||||||
|
watch_rtc_register_periodic_callback(cb_fast_tick, 128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void _movement_disable_fast_tick_if_possible() {
|
||||||
|
if ((movement_state.light_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _movement_handle_background_tasks() {
|
void _movement_handle_background_tasks() {
|
||||||
for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) {
|
for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) {
|
||||||
// For each face, if the watch face wants a background task...
|
// For each face, if the watch face wants a background task...
|
||||||
@ -83,17 +98,20 @@ void _movement_handle_background_tasks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void movement_request_tick_frequency(uint8_t freq) {
|
void movement_request_tick_frequency(uint8_t freq) {
|
||||||
watch_rtc_disable_all_periodic_callbacks();
|
if (freq == 128) return; // Movement uses the 128 Hz tick internally
|
||||||
|
RTC->MODE2.INTENCLR.reg = 0xFE; // disable all callbacks except the 128 Hz one
|
||||||
movement_state.subsecond = 0;
|
movement_state.subsecond = 0;
|
||||||
movement_state.tick_frequency = freq;
|
movement_state.tick_frequency = freq;
|
||||||
watch_rtc_register_periodic_callback(cb_tick, freq);
|
watch_rtc_register_periodic_callback(cb_tick, freq);
|
||||||
}
|
}
|
||||||
|
|
||||||
void movement_illuminate_led() {
|
void movement_illuminate_led() {
|
||||||
|
if (movement_state.settings.bit.led_duration) {
|
||||||
watch_set_led_color(movement_state.settings.bit.led_red_color ? (0xF | movement_state.settings.bit.led_red_color << 4) : 0,
|
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);
|
movement_state.settings.bit.led_green_color ? (0xF | movement_state.settings.bit.led_green_color << 4) : 0);
|
||||||
movement_state.led_on = true;
|
movement_state.light_ticks = (movement_state.settings.bit.led_duration * 2 - 1) * 128;
|
||||||
movement_state.light_ticks = movement_state.settings.bit.led_duration * 2;
|
_movement_enable_fast_tick_if_needed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void movement_move_to_face(uint8_t watch_face_index) {
|
void movement_move_to_face(uint8_t watch_face_index) {
|
||||||
@ -113,6 +131,7 @@ void app_init() {
|
|||||||
movement_state.settings.bit.le_interval = 1;
|
movement_state.settings.bit.le_interval = 1;
|
||||||
movement_state.settings.bit.led_duration = 1;
|
movement_state.settings.bit.led_duration = 1;
|
||||||
movement_state.settings.bit.time_zone = 16; // default to GMT
|
movement_state.settings.bit.time_zone = 16; // default to GMT
|
||||||
|
movement_state.light_ticks = -1;
|
||||||
_movement_reset_inactivity_countdown();
|
_movement_reset_inactivity_countdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,14 +201,15 @@ bool app_loop() {
|
|||||||
movement_state.watch_face_changed = false;
|
movement_state.watch_face_changed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the LED is on and should be off, turn it off
|
// if the LED should be off, turn it off
|
||||||
if (movement_state.led_on && movement_state.light_ticks == 0) {
|
if (movement_state.light_ticks == 0) {
|
||||||
// unless the user is holding down the LIGHT button, in which case, give them more time.
|
// unless the user is holding down the LIGHT button, in which case, give them more time.
|
||||||
if (watch_get_pin_level(BTN_LIGHT)) {
|
if (watch_get_pin_level(BTN_LIGHT)) {
|
||||||
movement_state.light_ticks = 3;
|
movement_state.light_ticks = 1;
|
||||||
} else {
|
} else {
|
||||||
watch_set_led_off();
|
watch_set_led_off();
|
||||||
movement_state.led_on = false;
|
movement_state.light_ticks = -1;
|
||||||
|
_movement_disable_fast_tick_if_possible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,35 +258,45 @@ bool app_loop() {
|
|||||||
|
|
||||||
event.subsecond = 0;
|
event.subsecond = 0;
|
||||||
|
|
||||||
return can_sleep && !movement_state.led_on;
|
return can_sleep && (movement_state.light_ticks == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
movement_event_type_t _figure_out_button_event(movement_event_type_t button_down_event_type, uint8_t *down_timestamp) {
|
movement_event_type_t _figure_out_button_event(bool pin_level, movement_event_type_t button_down_event_type, uint8_t *down_timestamp) {
|
||||||
watch_date_time date_time = watch_rtc_get_date_time();
|
if (pin_level) {
|
||||||
if (*down_timestamp) {
|
// handle rising edge
|
||||||
uint8_t diff = ((61 + date_time.unit.second) - *down_timestamp) % 60;
|
_movement_enable_fast_tick_if_needed();
|
||||||
*down_timestamp = 0;
|
*down_timestamp = movement_state.fast_ticks + 1;
|
||||||
if (diff > 1) return button_down_event_type + 2;
|
|
||||||
else return button_down_event_type + 1;
|
|
||||||
} else {
|
|
||||||
*down_timestamp = date_time.unit.second + 1;
|
|
||||||
return button_down_event_type;
|
return button_down_event_type;
|
||||||
|
} else {
|
||||||
|
// this line is hack but it handles the situation where the light button was held for more than 10 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.
|
||||||
|
if (diff > 64) return button_down_event_type + 2;
|
||||||
|
else return button_down_event_type + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cb_light_btn_interrupt() {
|
void cb_light_btn_interrupt() {
|
||||||
|
bool pin_level = watch_get_pin_level(BTN_LIGHT);
|
||||||
_movement_reset_inactivity_countdown();
|
_movement_reset_inactivity_countdown();
|
||||||
event.event_type = _figure_out_button_event(EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp);
|
event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cb_mode_btn_interrupt() {
|
void cb_mode_btn_interrupt() {
|
||||||
|
bool pin_level = watch_get_pin_level(BTN_MODE);
|
||||||
_movement_reset_inactivity_countdown();
|
_movement_reset_inactivity_countdown();
|
||||||
event.event_type = _figure_out_button_event(EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp);
|
event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cb_alarm_btn_interrupt() {
|
void cb_alarm_btn_interrupt() {
|
||||||
|
bool pin_level = watch_get_pin_level(BTN_ALARM);
|
||||||
_movement_reset_inactivity_countdown();
|
_movement_reset_inactivity_countdown();
|
||||||
event.event_type = _figure_out_button_event(EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp);
|
event.event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cb_alarm_btn_extwake() {
|
void cb_alarm_btn_extwake() {
|
||||||
@ -278,14 +308,18 @@ void cb_alarm_fired() {
|
|||||||
movement_state.needs_background_tasks_handled = true;
|
movement_state.needs_background_tasks_handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cb_fast_tick() {
|
||||||
|
movement_state.fast_ticks++;
|
||||||
|
if (movement_state.light_ticks > 0) movement_state.light_ticks--;
|
||||||
|
// this is just a fail-safe; fast tick should be disabled as soon as the button is up and/or the LED times out.
|
||||||
|
// but if for whatever reason it isn't, this forces the fast tick off after 10 seconds.
|
||||||
|
if (movement_state.fast_ticks >= 1280) watch_rtc_disable_periodic_callback(128);
|
||||||
|
}
|
||||||
|
|
||||||
void cb_tick() {
|
void cb_tick() {
|
||||||
event.event_type = EVENT_TICK;
|
event.event_type = EVENT_TICK;
|
||||||
watch_date_time date_time = watch_rtc_get_date_time();
|
watch_date_time date_time = watch_rtc_get_date_time();
|
||||||
if (date_time.unit.second != movement_state.last_second) {
|
if (date_time.unit.second != movement_state.last_second) {
|
||||||
// TODO: since we time the LED with the 1 Hz tick, the actual time lit can vary depending on whether the
|
|
||||||
// user hit it just before or just after a tick. If we time this with the system tick we can do better.
|
|
||||||
if (movement_state.light_ticks) movement_state.light_ticks--;
|
|
||||||
|
|
||||||
// TODO: can we consolidate these two ticks?
|
// 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.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--;
|
if (movement_state.timeout_ticks > 0) movement_state.timeout_ticks--;
|
||||||
|
@ -210,10 +210,11 @@ typedef struct {
|
|||||||
int16_t current_watch_face;
|
int16_t current_watch_face;
|
||||||
int16_t next_watch_face;
|
int16_t next_watch_face;
|
||||||
bool watch_face_changed;
|
bool watch_face_changed;
|
||||||
|
bool fast_tick_enabled;
|
||||||
|
int16_t fast_ticks;
|
||||||
|
|
||||||
// LED stuff
|
// LED stuff
|
||||||
uint8_t light_ticks;
|
int16_t light_ticks;
|
||||||
bool led_on;
|
|
||||||
|
|
||||||
// button tracking for long press
|
// button tracking for long press
|
||||||
uint8_t light_down_timestamp;
|
uint8_t light_down_timestamp;
|
||||||
|
@ -131,11 +131,7 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings
|
|||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
if (settings->bit.led_duration) {
|
if (settings->bit.led_duration) {
|
||||||
// TODO: since we time the LED with the 1 Hz tick, the actual time lit can vary depending
|
sprintf(buf, " %1d SeC", settings->bit.led_duration * 2 - 1);
|
||||||
// on whether the user hit it just before or just after a tick. so the setting is "1-2 s",
|
|
||||||
// "3-4 s", or "5-6 s". If we time this with the system tick we can do better.
|
|
||||||
// see also cb_tick at the bottom of movement.c
|
|
||||||
sprintf(buf, " %1d-%1d s", settings->bit.led_duration * 2 - 1, settings->bit.led_duration * 2);
|
|
||||||
watch_display_string(buf, 4);
|
watch_display_string(buf, 4);
|
||||||
} else {
|
} else {
|
||||||
watch_display_string("no LEd", 4);
|
watch_display_string("no LEd", 4);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user