Add Couch-To-5k training face
This commit is contained in:
parent
b162dc0663
commit
cde6ae3598
@ -118,6 +118,7 @@ SRCS += \
|
||||
../watch_faces/complication/flashlight_face.c \
|
||||
../watch_faces/clock/decimal_time_face.c \
|
||||
../watch_faces/clock/wyoscan_face.c \
|
||||
../watch_faces/complication/couchTo5k_face.c \
|
||||
# New watch faces go above this line.
|
||||
|
||||
# Leave this line at the bottom of the file; it has all the targets for making your project.
|
||||
|
@ -95,6 +95,7 @@
|
||||
#include "flashlight_face.h"
|
||||
#include "decimal_time_face.h"
|
||||
#include "wyoscan_face.h"
|
||||
#include "couchTo5k_face.h"
|
||||
// New includes go above this line.
|
||||
|
||||
#endif // MOVEMENT_FACES_H_
|
||||
|
264
movement/watch_faces/complication/couchTo5k_face.c
Normal file
264
movement/watch_faces/complication/couchTo5k_face.c
Normal file
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Ekaitz Zarraga <ekaitz@elenq.tech>
|
||||
*
|
||||
* 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 "couchTo5k_face.h"
|
||||
|
||||
// They go: Warmup, Run, Walk, Run, Walk, Run, Walk ... , End (0)
|
||||
// Time is defined in seconds
|
||||
// Maybe do /10 to reduce memory usage?
|
||||
// (i don't want to use floats)
|
||||
|
||||
// uint16_t C25K_WEEK_TEST[] = {10, 10, 10, 0};
|
||||
uint16_t C25K_WEEK_1[] = {300, 60, 90, 60, 90, 60, 90, 60, 90, 60, 90, 60,
|
||||
90, 60, 90, 60, 90, 0};
|
||||
uint16_t C25K_WEEK_2[] = {300, 90, 120, 90, 120, 90, 120, 90, 120, 90, 120,
|
||||
90, 120, 0};
|
||||
uint16_t C25K_WEEK_3[] = {300, 90, 90, 180, 180, 90, 90, 180, 180, 0};
|
||||
uint16_t C25K_WEEK_4[] = {300, 180, 90, 300, 150, 180, 90, 300, 0};
|
||||
uint16_t C25K_WEEK_5_1[] = {300, 300, 180, 300, 180, 300, 0 };
|
||||
uint16_t C25K_WEEK_5_2[] = {300, 480, 300, 480 , 0};
|
||||
uint16_t C25K_WEEK_5_3[] = {300, 1200, 0};
|
||||
uint16_t C25K_WEEK_6_1[] = {300, 300, 180, 480, 180, 300, 0 };
|
||||
uint16_t C25K_WEEK_6_2[] = {300, 600, 180, 600 , 0};
|
||||
uint16_t C25K_WEEK_6_3[] = {300, 1500, 0};
|
||||
uint16_t C25K_WEEK_7[] = {300, 1500, 0};
|
||||
uint16_t C25K_WEEK_8[] = {300, 1680, 0};
|
||||
uint16_t C25K_WEEK_9[] = {300, 1800, 0};
|
||||
|
||||
|
||||
#define C25K_SESSIONS_LENGTH 3*9
|
||||
uint16_t *C25K_SESSIONS[C25K_SESSIONS_LENGTH];
|
||||
|
||||
static inline bool _finished(couchTo5k_state_t *state){
|
||||
return state->exercise_type == C25K_FINISHED;
|
||||
}
|
||||
static inline bool _cleared(couchTo5k_state_t *state){
|
||||
return state->timer == C25K_SESSIONS[state->session][0]
|
||||
&& state->exercise == 0;
|
||||
}
|
||||
static inline void _next_session(couchTo5k_state_t *state){
|
||||
if (++state->session >= C25K_SESSIONS_LENGTH){
|
||||
state->session = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void _assign_exercise_type(couchTo5k_state_t *state){
|
||||
if (state->exercise == 0){
|
||||
state->exercise_type = C25K_WARMUP;
|
||||
} else if (state->exercise % 2 == 1){
|
||||
state->exercise_type = C25K_RUN;
|
||||
} else {
|
||||
state->exercise_type = C25K_WALK;
|
||||
}
|
||||
}
|
||||
|
||||
static void _next_exercise(couchTo5k_state_t *state){
|
||||
state->exercise++;
|
||||
state->timer = C25K_SESSIONS[state->session][state->exercise];
|
||||
// If the new timer starts in zero, it's finished
|
||||
if (state->timer == 0){
|
||||
movement_play_alarm_beeps(7, BUZZER_NOTE_C8);
|
||||
state->exercise_type = C25K_FINISHED;
|
||||
return;
|
||||
}
|
||||
movement_play_alarm_beeps(4, BUZZER_NOTE_A7);
|
||||
_assign_exercise_type(state);
|
||||
}
|
||||
|
||||
static void _init_session(couchTo5k_state_t *state){
|
||||
state->exercise = 0; // Restart exercise counter
|
||||
state->timer = C25K_SESSIONS[state->session][state->exercise];
|
||||
_assign_exercise_type(state);
|
||||
}
|
||||
|
||||
static char *_exercise_type_to_str(exercise_type_t t){
|
||||
switch (t){
|
||||
case C25K_WARMUP:
|
||||
return "WU";
|
||||
case C25K_RUN:
|
||||
return "RU";
|
||||
case C25K_WALK:
|
||||
return "WA";
|
||||
case C25K_FINISHED:
|
||||
return "--";
|
||||
default:
|
||||
return " ";
|
||||
}
|
||||
}
|
||||
static void _display(couchTo5k_state_t *state, char *buf){
|
||||
// TODO only repaint needed parts
|
||||
uint8_t seconds = state->timer % 60;
|
||||
sprintf(buf, "%s%2d%2d%02d%02d",
|
||||
_exercise_type_to_str(state->exercise_type),
|
||||
(state->session + 1) % 100,
|
||||
((state->timer - seconds) / 60) % 100,
|
||||
seconds,
|
||||
(state->exercise + 1) % 100);
|
||||
watch_display_string(buf, 0);
|
||||
}
|
||||
|
||||
|
||||
void couchTo5k_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(couchTo5k_state_t));
|
||||
memset(*context_ptr, 0, sizeof(couchTo5k_state_t));
|
||||
// Do any one-time tasks in here; the inside of this conditional
|
||||
// happens only at boot.
|
||||
// C25K_SESSIONS[0] = C25K_WEEK_TEST;
|
||||
C25K_SESSIONS[0] = C25K_WEEK_1;
|
||||
C25K_SESSIONS[1] = C25K_WEEK_1;
|
||||
C25K_SESSIONS[2] = C25K_WEEK_1;
|
||||
C25K_SESSIONS[3] = C25K_WEEK_2;
|
||||
C25K_SESSIONS[4] = C25K_WEEK_2;
|
||||
C25K_SESSIONS[5] = C25K_WEEK_2;
|
||||
C25K_SESSIONS[6] = C25K_WEEK_3;
|
||||
C25K_SESSIONS[7] = C25K_WEEK_3;
|
||||
C25K_SESSIONS[8] = C25K_WEEK_3;
|
||||
C25K_SESSIONS[9] = C25K_WEEK_4;
|
||||
C25K_SESSIONS[10] = C25K_WEEK_4;
|
||||
C25K_SESSIONS[11] = C25K_WEEK_4;
|
||||
C25K_SESSIONS[12] = C25K_WEEK_5_1;
|
||||
C25K_SESSIONS[13] = C25K_WEEK_5_2;
|
||||
C25K_SESSIONS[14] = C25K_WEEK_5_3;
|
||||
C25K_SESSIONS[15] = C25K_WEEK_6_1;
|
||||
C25K_SESSIONS[16] = C25K_WEEK_6_2;
|
||||
C25K_SESSIONS[17] = C25K_WEEK_6_3;
|
||||
C25K_SESSIONS[18] = C25K_WEEK_7;
|
||||
C25K_SESSIONS[19] = C25K_WEEK_7;
|
||||
C25K_SESSIONS[20] = C25K_WEEK_7;
|
||||
C25K_SESSIONS[21] = C25K_WEEK_8;
|
||||
C25K_SESSIONS[22] = C25K_WEEK_8;
|
||||
C25K_SESSIONS[23] = C25K_WEEK_8;
|
||||
C25K_SESSIONS[24] = C25K_WEEK_9;
|
||||
C25K_SESSIONS[25] = C25K_WEEK_9;
|
||||
C25K_SESSIONS[26] = C25K_WEEK_9;
|
||||
}
|
||||
// Do any pin or peripheral setup here; this will be called whenever the
|
||||
// watch wakes from deep sleep.
|
||||
}
|
||||
|
||||
void couchTo5k_face_activate(movement_settings_t *settings, void *context) {
|
||||
(void) settings;
|
||||
(void) context;
|
||||
// Handle any tasks related to your watch face coming on screen.
|
||||
watch_set_colon();
|
||||
}
|
||||
|
||||
|
||||
bool couchTo5k_face_loop(movement_event_t event, movement_settings_t *settings,
|
||||
void *context) {
|
||||
couchTo5k_state_t *state = (couchTo5k_state_t *)context;
|
||||
static char buf[11];
|
||||
static bool paused = true;
|
||||
|
||||
switch (event.event_type) {
|
||||
case EVENT_ACTIVATE:
|
||||
// Show your initial UI here.
|
||||
movement_request_tick_frequency(1);
|
||||
_init_session(state);
|
||||
paused = true;
|
||||
_display(state, buf);
|
||||
break;
|
||||
case EVENT_TICK:
|
||||
if ( !paused && !_finished(state) ) {
|
||||
if (state->timer == 0){
|
||||
_next_exercise(state);
|
||||
} else {
|
||||
state->timer--;
|
||||
}
|
||||
}
|
||||
_display(state, buf);
|
||||
break;
|
||||
case EVENT_LIGHT_BUTTON_UP:
|
||||
// This is the next-exercise / reset button.
|
||||
|
||||
// When finished move to the next session and leave it paused
|
||||
if ( _finished(state) ){
|
||||
_next_session(state);
|
||||
_init_session(state);
|
||||
paused = true;
|
||||
break;
|
||||
}
|
||||
// When paused and cleared move to next, when only paused, clear
|
||||
if ( paused ) {
|
||||
if ( _cleared(state) ){
|
||||
_next_session(state);
|
||||
}
|
||||
_init_session(state);
|
||||
}
|
||||
break;
|
||||
case EVENT_ALARM_BUTTON_UP:
|
||||
paused = !paused;
|
||||
break;
|
||||
case EVENT_TIMEOUT:
|
||||
// Your watch face will receive this event after a period of
|
||||
// inactivity. If it makes sense to resign,
|
||||
// movement_move_to_face(0);
|
||||
break;
|
||||
case EVENT_LOW_ENERGY_UPDATE:
|
||||
// If you did not resign in EVENT_TIMEOUT, you can use this event
|
||||
// to update the display once a minute. Avoid displaying
|
||||
// fast-updating values like seconds, since the display won't
|
||||
// update again for 60 seconds. You should also consider starting
|
||||
// the tick animation, to show the wearer that this is sleep mode:
|
||||
// watch_start_tick_animation(500);
|
||||
break;
|
||||
default:
|
||||
// Movement's default loop handler will step in for any cases you
|
||||
// don't handle above:
|
||||
// * EVENT_LIGHT_BUTTON_DOWN lights the LED
|
||||
// * EVENT_MODE_BUTTON_UP moves to the next watch face in the list
|
||||
// * EVENT_MODE_LONG_PRESS returns to the first watch face (or
|
||||
// skips to the secondary watch face, if configured)
|
||||
// You can override any of these behaviors by adding a case for
|
||||
// these events to this switch statement.
|
||||
return movement_default_loop_handler(event, settings);
|
||||
}
|
||||
|
||||
// return true if the watch can enter standby mode. Generally speaking, you
|
||||
// should always return true.
|
||||
// Exceptions:
|
||||
// * If you are displaying a color using the low-level watch_set_led_color
|
||||
// function, you should return false.
|
||||
// * If you are sounding the buzzer using the low-level
|
||||
// watch_set_buzzer_on function, you should return false.
|
||||
// Note that if you are driving the LED or buzzer using Movement functions
|
||||
// like movement_illuminate_led or movement_play_alarm, you can still
|
||||
// return true. This guidance only applies to the low-level watch_
|
||||
// functions.
|
||||
return true;
|
||||
}
|
||||
|
||||
void couchTo5k_face_resign(movement_settings_t *settings, void *context) {
|
||||
(void) settings;
|
||||
(void) context;
|
||||
|
||||
// handle any cleanup before your watch face goes off-screen.
|
||||
}
|
||||
|
87
movement/watch_faces/complication/couchTo5k_face.h
Normal file
87
movement/watch_faces/complication/couchTo5k_face.h
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Ekaitz Zarraga <ekaitz@elenq.tech>
|
||||
*
|
||||
* 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 COUCHTO5K_FACE_H_
|
||||
#define COUCHTO5K_FACE_H_
|
||||
|
||||
#include "movement.h"
|
||||
|
||||
/*
|
||||
* Couch To 5k;
|
||||
*
|
||||
*
|
||||
* The program is designed to train 3 times a week. Each training is a
|
||||
* *session*. Each of the rounds you have in the training is an *exercise*.
|
||||
*
|
||||
* The training goes like this:
|
||||
* 5min warm-up walk -> Run X minutes -> Walk Y minutes -> ... -> Stop
|
||||
*
|
||||
* The watch face shows it like this: The weekday indicator shows if you need
|
||||
* to Warm Up (`WU`), run (`rU`), walk (`WA`) or stop (`--`).
|
||||
*
|
||||
* The month-day indicator shows the session you are in (from 1 to 27).
|
||||
*
|
||||
* The timer shows the time you have left in the exercise and the exercise you
|
||||
* are doing (MM:SS:ee). When an exercise finishes you are notified with an
|
||||
* alarm. When the whole session finishes, a different tone is played for a
|
||||
* longer period.
|
||||
*
|
||||
* Pressing the ALARM button pauses/resumes the clock.
|
||||
*
|
||||
* Pressing the LIGHT button does nothing if the timer is not paused. When it
|
||||
* is paused it clears the current session (it restarts it to the beginning)
|
||||
* and if it was already cleared or the current session was finished moves to
|
||||
* the next session.
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
C25K_WARMUP,
|
||||
C25K_RUN,
|
||||
C25K_WALK,
|
||||
C25K_FINISHED
|
||||
} exercise_type_t;
|
||||
|
||||
typedef struct {
|
||||
// Anything you need to keep track of, put it here!
|
||||
uint8_t session;
|
||||
uint8_t exercise;
|
||||
exercise_type_t exercise_type;
|
||||
uint16_t timer;
|
||||
} couchTo5k_state_t;
|
||||
|
||||
void couchTo5k_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
|
||||
void couchTo5k_face_activate(movement_settings_t *settings, void *context);
|
||||
bool couchTo5k_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
|
||||
void couchTo5k_face_resign(movement_settings_t *settings, void *context);
|
||||
|
||||
#define couchTo5k_face ((const watch_face_t){ \
|
||||
couchTo5k_face_setup, \
|
||||
couchTo5k_face_activate, \
|
||||
couchTo5k_face_loop, \
|
||||
couchTo5k_face_resign, \
|
||||
NULL, \
|
||||
})
|
||||
|
||||
#endif // COUCHTO5K_FACE_H_
|
||||
|
Loading…
x
Reference in New Issue
Block a user