mirror of
https://github.com/joeycastillo/second-movement.git
synced 2025-10-28 19:15:44 +00:00
add Baby kicks face (#70)
This commit is contained in:
parent
6a42269857
commit
3d86e14f05
@ -63,6 +63,7 @@
|
||||
#include "tally_face.h"
|
||||
#include "probability_face.h"
|
||||
#include "ke_decimal_time_face.h"
|
||||
#include "baby_kicks_face.h"
|
||||
#include "counter_face.h"
|
||||
#include "pulsometer_face.h"
|
||||
#include "interval_face.h"
|
||||
|
||||
@ -38,6 +38,7 @@ SRCS += \
|
||||
./watch-faces/complication/kitchen_conversions_face.c \
|
||||
./watch-faces/complication/periodic_table_face.c \
|
||||
./watch-faces/clock/ke_decimal_time_face.c \
|
||||
./watch-faces/complication/baby_kicks_face.c \
|
||||
./watch-faces/complication/counter_face.c \
|
||||
./watch-faces/complication/pulsometer_face.c \
|
||||
./watch-faces/complication/interval_face.c \
|
||||
|
||||
439
watch-faces/complication/baby_kicks_face.c
Normal file
439
watch-faces/complication/baby_kicks_face.c
Normal file
@ -0,0 +1,439 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2025 Gábor Nyéki
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "baby_kicks_face.h"
|
||||
#include "watch.h"
|
||||
#include "watch_utility.h"
|
||||
|
||||
static inline void _play_failure_sound_if_beep_is_on() {
|
||||
if (movement_button_should_sound()) {
|
||||
watch_buzzer_play_note(BUZZER_NOTE_E7, 10);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void _play_successful_increment_sound_if_beep_is_on() {
|
||||
if (movement_button_should_sound()) {
|
||||
watch_buzzer_play_note(BUZZER_NOTE_E6, 10);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void _play_successful_decrement_sound_if_beep_is_on() {
|
||||
if (movement_button_should_sound()) {
|
||||
watch_buzzer_play_note(BUZZER_NOTE_D6, 10);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void _play_button_sound_if_beep_is_on() {
|
||||
if (movement_button_should_sound()) {
|
||||
watch_buzzer_play_note(BUZZER_NOTE_C7, 10);
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Predicate for whether the counter has been started.
|
||||
*/
|
||||
static inline bool _is_running(baby_kicks_state_t *state) {
|
||||
return state->start > 0;
|
||||
}
|
||||
|
||||
/** @brief Gets the current time, and caches it for re-use.
|
||||
*/
|
||||
static inline watch_date_time_t *_get_now(baby_kicks_state_t *state) {
|
||||
if (state->now.unit.year == 0) {
|
||||
state->now = movement_get_local_date_time();
|
||||
}
|
||||
|
||||
return &state->now;
|
||||
}
|
||||
|
||||
/** @brief Clears the current time. Should only be called at the end of
|
||||
* `baby_kicks_face_loop`.
|
||||
*/
|
||||
static inline void _clear_now(baby_kicks_state_t *state) {
|
||||
if (state->now.unit.year > 0) {
|
||||
memset(&state->now, 0, sizeof(state->now));
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Calculates the number of minutes since the timer was started.
|
||||
* @return If the counter has been started, then the number of full
|
||||
* minutes that have elapsed. If it has not been started, then
|
||||
* 255.
|
||||
*/
|
||||
static inline uint32_t _elapsed_minutes(baby_kicks_state_t *state) {
|
||||
if (!_is_running(state)) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
watch_date_time_t *now = _get_now(state);
|
||||
|
||||
return (
|
||||
watch_utility_date_time_to_unix_time(*now, 0) - state->start
|
||||
) / 60;
|
||||
}
|
||||
|
||||
/** @brief Predicate for whether the counter has started but run for too
|
||||
* long.
|
||||
*/
|
||||
static inline bool _has_timed_out(baby_kicks_state_t *state) {
|
||||
return _elapsed_minutes(state) > BABY_KICKS_TIMEOUT;
|
||||
}
|
||||
|
||||
/** @brief Determines what we should display based on `state`. Should
|
||||
* only be called from `baby_kicks_face_loop`.
|
||||
*/
|
||||
static void _update_display_mode(baby_kicks_state_t *state) {
|
||||
if (watch_sleep_animation_is_running()) {
|
||||
state->mode = BABY_KICKS_MODE_LE_MODE;
|
||||
} else if (!_is_running(state)) {
|
||||
state->mode = BABY_KICKS_MODE_SPLASH;
|
||||
} else if (_has_timed_out(state)) {
|
||||
state->mode = BABY_KICKS_MODE_TIMED_OUT;
|
||||
} else {
|
||||
state->mode = BABY_KICKS_MODE_ACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Starts the counter.
|
||||
* @details Sets the start time which will be used to calculate the
|
||||
* elapsed minutes.
|
||||
*/
|
||||
static inline void _start(baby_kicks_state_t *state) {
|
||||
watch_date_time_t *now = _get_now(state);
|
||||
uint32_t now_unix = watch_utility_date_time_to_unix_time(*now, 0);
|
||||
|
||||
state->start = now_unix;
|
||||
}
|
||||
|
||||
/** @brief Resets the counter.
|
||||
* @details Zeros out the watch face state and clears the undo ring
|
||||
* buffer. Effectively sets `state->mode` to
|
||||
* `BABY_KICKS_MODE_SPLASH`.
|
||||
*/
|
||||
static void _reset(baby_kicks_state_t *state) {
|
||||
memset(state, 0, sizeof(baby_kicks_state_t));
|
||||
memset(
|
||||
state->undo_buffer.stretches,
|
||||
0xff,
|
||||
sizeof(state->undo_buffer.stretches)
|
||||
);
|
||||
}
|
||||
|
||||
/** @brief Records a movement.
|
||||
* @details Increments the movement counter, and along with it, if
|
||||
* necessary, the counter of one-minute stretches. Also adds
|
||||
* the movement to the undo buffer.
|
||||
*/
|
||||
static inline void _increment_counts(baby_kicks_state_t *state) {
|
||||
watch_date_time_t *now = _get_now(state);
|
||||
uint32_t now_unix = watch_utility_date_time_to_unix_time(*now, 0);
|
||||
|
||||
/* Add movement to the undo ring buffer. */
|
||||
state->undo_buffer.stretches[state->undo_buffer.head] =
|
||||
state->stretch_count;
|
||||
state->undo_buffer.head++;
|
||||
state->undo_buffer.head %= sizeof(state->undo_buffer.stretches);
|
||||
|
||||
state->movement_count++;
|
||||
|
||||
if (state->stretch_count == 0
|
||||
|| state->latest_stretch_start + 60 < now_unix) {
|
||||
/* Start new stretch. */
|
||||
state->latest_stretch_start = now_unix;
|
||||
state->stretch_count++;
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Undoes the last movement.
|
||||
* @details Decrements the movement counter and, if necessary, the
|
||||
* counter of one-minute stretches.
|
||||
* @return True if and only if there was a movement to undo.
|
||||
*/
|
||||
static inline bool _successfully_undo(baby_kicks_state_t *state) {
|
||||
uint8_t latest_mvmt, pre_undo_stretch_count;
|
||||
|
||||
/* The latest movement is stored one position before `.head`. */
|
||||
if (state->undo_buffer.head == 0) {
|
||||
latest_mvmt = sizeof(state->undo_buffer.stretches) - 1;
|
||||
} else {
|
||||
latest_mvmt = state->undo_buffer.head - 1;
|
||||
}
|
||||
|
||||
pre_undo_stretch_count =
|
||||
state->undo_buffer.stretches[latest_mvmt];
|
||||
|
||||
if (pre_undo_stretch_count == 0xff) {
|
||||
/* Nothing to undo. */
|
||||
return false;
|
||||
} else if (pre_undo_stretch_count < state->stretch_count) {
|
||||
state->latest_stretch_start = 0;
|
||||
state->stretch_count--;
|
||||
}
|
||||
|
||||
state->movement_count--;
|
||||
|
||||
state->undo_buffer.stretches[latest_mvmt] = 0xff;
|
||||
state->undo_buffer.head = latest_mvmt;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @brief Updates the display with the movement counts if the counter
|
||||
* has been started.
|
||||
*/
|
||||
static inline void _display_counts(baby_kicks_state_t *state) {
|
||||
if (!_is_running(state)) {
|
||||
watch_display_text(WATCH_POSITION_BOTTOM, "baby ");
|
||||
watch_clear_colon();
|
||||
} else {
|
||||
char buf[7];
|
||||
|
||||
snprintf(
|
||||
buf,
|
||||
sizeof(buf),
|
||||
"%2d%4d",
|
||||
state->stretch_count,
|
||||
state->movement_count
|
||||
);
|
||||
|
||||
watch_display_text(WATCH_POSITION_BOTTOM, buf);
|
||||
watch_set_colon();
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Updates the display with the number of minutes since the
|
||||
* timer was started.
|
||||
* @details If more than `BABY_KICKS_TIMEOUT` minutes have elapsed,
|
||||
* then it displays "TO".
|
||||
*/
|
||||
static void _display_elapsed_minutes(baby_kicks_state_t *state) {
|
||||
if (!_is_running(state)) {
|
||||
watch_display_text(WATCH_POSITION_TOP_LEFT, " ");
|
||||
watch_display_text(WATCH_POSITION_TOP_RIGHT, " ");
|
||||
} else if (_has_timed_out(state)) {
|
||||
watch_display_text(WATCH_POSITION_TOP_LEFT, "TO");
|
||||
watch_display_text(WATCH_POSITION_TOP_RIGHT, " ");
|
||||
} else {
|
||||
/* NOTE We display the elapsed minutes in two parts. This is
|
||||
* because on the classic LCD, neither the "weekday digits" nor
|
||||
* the "day digits" position is suitable to display the elapsed
|
||||
* minutes:
|
||||
*
|
||||
* - The classic LCD cannot display 2, 4, 5, 6, or 9 as the last
|
||||
* digit in the "weekday digits" position.
|
||||
* - It cannot display any number greater than 3 as the first
|
||||
* digit in the "day digits" position.
|
||||
*
|
||||
* As a workaround, we split the elapsed minutes into 30-minute
|
||||
* "laps." The elapsed minutes in the current "lap" are shown
|
||||
* in the "day digits" position. This is any number between 0
|
||||
* and 29. The elapsed minutes in past "laps" are shown in the
|
||||
* "weekday digits" position. This is either nothing, 30, 60,
|
||||
* or 90.
|
||||
*
|
||||
* The sum of the numbers shown in the two positions is equal to
|
||||
* the total elapsed minutes.
|
||||
*/
|
||||
|
||||
char buf[3];
|
||||
uint32_t elapsed_minutes = _elapsed_minutes(state);
|
||||
uint8_t multiple = elapsed_minutes / 30;
|
||||
uint8_t remainder = elapsed_minutes % 30;
|
||||
|
||||
if (multiple == 0) {
|
||||
watch_display_text(WATCH_POSITION_TOP_LEFT, " ");
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "%2d", multiple * 30);
|
||||
watch_display_text(WATCH_POSITION_TOP_LEFT, buf);
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "%2d", remainder);
|
||||
watch_display_text(WATCH_POSITION_TOP_RIGHT, buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void _update_display(baby_kicks_state_t *state) {
|
||||
_display_counts(state);
|
||||
_display_elapsed_minutes(state);
|
||||
}
|
||||
|
||||
static inline void _start_sleep_face() {
|
||||
if (!watch_sleep_animation_is_running()) {
|
||||
watch_display_text(WATCH_POSITION_TOP_LEFT, " ");
|
||||
watch_display_text(WATCH_POSITION_TOP_RIGHT, " ");
|
||||
watch_display_text(WATCH_POSITION_BOTTOM, "baby ");
|
||||
|
||||
watch_clear_colon();
|
||||
|
||||
watch_start_sleep_animation(500);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void _stop_sleep_face() {
|
||||
if (watch_sleep_animation_is_running()) {
|
||||
watch_stop_sleep_animation();
|
||||
}
|
||||
}
|
||||
|
||||
void baby_kicks_face_setup(uint8_t watch_face_index,
|
||||
void **context_ptr) {
|
||||
(void) watch_face_index;
|
||||
|
||||
if (*context_ptr == NULL) {
|
||||
*context_ptr = malloc(sizeof(baby_kicks_state_t));
|
||||
_reset(*context_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void baby_kicks_face_activate(void *context) {
|
||||
(void) context;
|
||||
|
||||
_stop_sleep_face();
|
||||
}
|
||||
|
||||
void baby_kicks_face_resign(void *context) {
|
||||
baby_kicks_state_t *state = (baby_kicks_state_t *)context;
|
||||
|
||||
state->currently_displayed = false;
|
||||
}
|
||||
|
||||
bool baby_kicks_face_loop(movement_event_t event, void *context) {
|
||||
baby_kicks_state_t *state = (baby_kicks_state_t *)context;
|
||||
|
||||
switch (event.event_type) {
|
||||
case EVENT_ACTIVATE:
|
||||
state->currently_displayed = true;
|
||||
_update_display_mode(state);
|
||||
_update_display(state);
|
||||
break;
|
||||
case EVENT_ALARM_BUTTON_UP: /* Start or increment. */
|
||||
/* Update `state->mode` in case we have a running counter
|
||||
* that has just timed out. */
|
||||
_update_display_mode(state);
|
||||
|
||||
switch (state->mode) {
|
||||
case BABY_KICKS_MODE_SPLASH:
|
||||
_start(state);
|
||||
_update_display_mode(state);
|
||||
_update_display(state);
|
||||
_play_button_sound_if_beep_is_on();
|
||||
break;
|
||||
case BABY_KICKS_MODE_ACTIVE:
|
||||
_increment_counts(state);
|
||||
_update_display(state);
|
||||
_play_successful_increment_sound_if_beep_is_on();
|
||||
break;
|
||||
case BABY_KICKS_MODE_TIMED_OUT:
|
||||
_play_failure_sound_if_beep_is_on();
|
||||
break;
|
||||
case BABY_KICKS_MODE_LE_MODE: /* fallthrough */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case EVENT_ALARM_LONG_PRESS: /* Undo. */
|
||||
_update_display_mode(state);
|
||||
|
||||
switch (state->mode) {
|
||||
case BABY_KICKS_MODE_ACTIVE:
|
||||
if (!_successfully_undo(state)) {
|
||||
_play_failure_sound_if_beep_is_on();
|
||||
} else {
|
||||
_update_display(state);
|
||||
_play_successful_decrement_sound_if_beep_is_on();
|
||||
}
|
||||
break;
|
||||
case BABY_KICKS_MODE_SPLASH: /* fallthrough */
|
||||
case BABY_KICKS_MODE_TIMED_OUT:
|
||||
_play_failure_sound_if_beep_is_on();
|
||||
break;
|
||||
case BABY_KICKS_MODE_LE_MODE: /* fallthrough */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case EVENT_MODE_LONG_PRESS: /* Reset. */
|
||||
_update_display_mode(state);
|
||||
|
||||
switch (state->mode) {
|
||||
case BABY_KICKS_MODE_ACTIVE: /* fallthrough */
|
||||
case BABY_KICKS_MODE_TIMED_OUT:
|
||||
_reset(state);
|
||||
|
||||
/* This shows the splash screen because `_reset`
|
||||
* sets `state->mode` to `BABY_KICKS_MODE_SPLASH`.
|
||||
*/
|
||||
_update_display(state);
|
||||
|
||||
_play_button_sound_if_beep_is_on();
|
||||
break;
|
||||
case BABY_KICKS_MODE_SPLASH:
|
||||
_play_failure_sound_if_beep_is_on();
|
||||
break;
|
||||
case BABY_KICKS_MODE_LE_MODE: /* fallthrough */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case EVENT_BACKGROUND_TASK: /* Update minute display. */
|
||||
_update_display_mode(state);
|
||||
|
||||
switch (state->mode) {
|
||||
case BABY_KICKS_MODE_ACTIVE: /* fallthrough */
|
||||
case BABY_KICKS_MODE_TIMED_OUT:
|
||||
if (state->currently_displayed) {
|
||||
_display_elapsed_minutes(state);
|
||||
}
|
||||
break;
|
||||
case BABY_KICKS_MODE_LE_MODE: /* fallthrough */
|
||||
case BABY_KICKS_MODE_SPLASH: /* fallthrough */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case EVENT_LOW_ENERGY_UPDATE:
|
||||
_start_sleep_face();
|
||||
break;
|
||||
default:
|
||||
movement_default_loop_handler(event);
|
||||
break;
|
||||
}
|
||||
|
||||
_clear_now(state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
movement_watch_face_advisory_t baby_kicks_face_advise(void *context) {
|
||||
movement_watch_face_advisory_t retval = { 0 };
|
||||
baby_kicks_state_t *state = (baby_kicks_state_t *)context;
|
||||
|
||||
retval.wants_background_task =
|
||||
state->mode == BABY_KICKS_MODE_ACTIVE;
|
||||
|
||||
return retval;
|
||||
}
|
||||
132
watch-faces/complication/baby_kicks_face.h
Normal file
132
watch-faces/complication/baby_kicks_face.h
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2025 Gábor Nyéki
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* Baby kicks face
|
||||
*
|
||||
* Count the movements of your in-utero baby.
|
||||
*
|
||||
* Background:
|
||||
*
|
||||
* This practice is recommended particularly in the third trimester
|
||||
* (from week 28 onwards). The exact recommendations vary as to how to
|
||||
* count the baby's movements. Some recommend drawing a chart with the
|
||||
* number of "kicks" within a 12-hour period:
|
||||
*
|
||||
* - https://en.wikipedia.org/wiki/Kick_chart
|
||||
*
|
||||
* Others recommend measuring the time that it takes for the baby to
|
||||
* "kick" 10 times:
|
||||
*
|
||||
* - https://my.clevelandclinic.org/health/articles/23497-kick-counts
|
||||
* - https://healthy.kaiserpermanente.org/health-wellness/health-encyclopedia/he.pregnancy-kick-counts.aa107042
|
||||
*
|
||||
* (Of course, not every movement that the baby makes is a kick, and we
|
||||
* are interested in all movements, not only kicks.)
|
||||
*
|
||||
* This watch face follows the latter set of recommendations. When you
|
||||
* start the counter, it measures the number of elapsed minutes, and it
|
||||
* tracks the number of movements as you increment the counter. Since
|
||||
* some consecutive movements made by the baby are actually part of a
|
||||
* longer maneuver, the watch face also displays the number of
|
||||
* one-minute stretches in which the baby moved at least once.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* - ALARM button, short press:
|
||||
* * start the counter if it isn't running
|
||||
* * increment the count otherwise
|
||||
* - ALARM button, long press: undo the last count
|
||||
* - MODE button, long press: reset the count to zero
|
||||
*
|
||||
* The watch face displays two numbers in the "clock digits" positions:
|
||||
*
|
||||
* 1. Count of movements (in the "second" and "minute" positions).
|
||||
* 2. Count of one-minute stretches in which at least one movement
|
||||
* occurred (in the "hour" position).
|
||||
*
|
||||
* The number of elapsed minutes, up to and including 29, is shown in
|
||||
* the "day digits" position. Due to the limitations of the classic LCD
|
||||
* display, completed 30-minute intervals are shown in the "weekday
|
||||
* digits" position. The total number of elapsed minutes is the sum of
|
||||
* these two numbers.
|
||||
*
|
||||
* The watch face times out after 99 minutes, since it cannot display
|
||||
* more than 99 one-minute stretches in the "hour" position. When this
|
||||
* happens, the "weekday digits" position shows "TO".
|
||||
*/
|
||||
|
||||
#include "movement.h"
|
||||
|
||||
typedef enum {
|
||||
BABY_KICKS_MODE_SPLASH = 0,
|
||||
BABY_KICKS_MODE_ACTIVE,
|
||||
BABY_KICKS_MODE_TIMED_OUT,
|
||||
BABY_KICKS_MODE_LE_MODE,
|
||||
} baby_kicks_mode_t;
|
||||
|
||||
/* Stop counting after 99 minutes. The classic LCD cannot display any
|
||||
* larger number in the "weekday digits" position. */
|
||||
#define BABY_KICKS_TIMEOUT 99
|
||||
|
||||
/* Ring buffer to store and allow undoing up to 10 movements. */
|
||||
typedef struct {
|
||||
/* For each movement in the undo buffer, this array stores the value
|
||||
* of `state->stretch_count` right before the movement was
|
||||
* recorded. This is used for decrementing `state->stretch_count`
|
||||
* as part of the undo operation if necessary. */
|
||||
uint8_t stretches[10];
|
||||
|
||||
/* Index of the next available slot in `.stretches`. */
|
||||
uint8_t head;
|
||||
} baby_kicks_undo_buffer_t;
|
||||
|
||||
typedef struct {
|
||||
bool currently_displayed;
|
||||
baby_kicks_mode_t mode;
|
||||
watch_date_time_t now;
|
||||
uint32_t start;
|
||||
uint32_t latest_stretch_start;
|
||||
uint8_t stretch_count; /* Between 0 and `BABY_KICKS_TIMEOUT`. */
|
||||
uint16_t movement_count; /* Between 0 and 9999. */
|
||||
baby_kicks_undo_buffer_t undo_buffer;
|
||||
} baby_kicks_state_t;
|
||||
|
||||
void baby_kicks_face_setup(uint8_t watch_face_index, void **context_ptr);
|
||||
void baby_kicks_face_activate(void *context);
|
||||
bool baby_kicks_face_loop(movement_event_t event, void *context);
|
||||
void baby_kicks_face_resign(void *context);
|
||||
movement_watch_face_advisory_t baby_kicks_face_advise(void *context);
|
||||
|
||||
#define baby_kicks_face ((const watch_face_t) { \
|
||||
baby_kicks_face_setup, \
|
||||
baby_kicks_face_activate, \
|
||||
baby_kicks_face_loop, \
|
||||
baby_kicks_face_resign, \
|
||||
baby_kicks_face_advise, \
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user