mirror of
https://github.com/firewalkwithm3/Sensor-Watch.git
synced 2024-11-22 19:20:30 +08:00
Sensor watch sailing face (#205)
* Update sailing_face.c Modified sailing_face. Added features, some of which @niclashoyer already suggested in his initial PR: - stopwatch-like counter after start signal - lap counter after start signal - optional additional sounds at every minute, 30s, 10s countdown - sound options (no sound, start only, signals only, all) - maximum starting time up to 10min (instead of 9) - improved timing, display is no longer delayed by sound * Update sailing_face.h Modified sailing_face. Added features, some of which @niclashoyer already suggested in his initial PR: - stopwatch-like counter after start signal - lap counter after start signal - optional additional sounds at every minute, 30s, 10s countdown - sound options (no sound, start only, signals only, all) - maximum starting time up to 10min (instead of 9) - improved timing, display is no longer delayed by sound
This commit is contained in:
parent
c814c780e3
commit
59ff549235
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Jan H. Voigt
|
||||
* Copyright (c) 2022 Wesley Ellis
|
||||
* Copyright (c) 2022 Niclas Hoyer
|
||||
*
|
||||
|
@ -31,6 +32,37 @@
|
|||
#include "watch.h"
|
||||
#include "watch_utility.h"
|
||||
|
||||
/*
|
||||
|
||||
Implements a sailing timer.
|
||||
|
||||
Usage:
|
||||
|
||||
Waiting mode: Light button enters settings, alarm button starts the timer (sailing mode).
|
||||
|
||||
Sailing mode:
|
||||
Alarm button switches to next programmed start signal, long press on light button
|
||||
resets timer and enters waiting mode. Countdown to zero, then switch to counting mode.
|
||||
|
||||
Counting mode:
|
||||
After the start signal (0s), the duration of the race is counted (like a stopwatch timer).
|
||||
Alarm button increases the lap counter, alarm long press resets lap counter.
|
||||
Long press on light button resets timer and enters waiting mode.
|
||||
|
||||
Setting mode:
|
||||
Alarm button increases active (blinking) signal. Goes to 0 if upper boundary
|
||||
(11 or whatever the signal left to the active one is set to) is met.
|
||||
10 is printed vertically (letter o plus top segment).
|
||||
Alarm button long press resets to default minutes (5-4-1-0).
|
||||
Light button cycles through the signals.
|
||||
Long press on light button cycles through sound modes:
|
||||
- Bell indicator: Sound at start (0s) only.
|
||||
- Signal indicator: Sound at each programmed signal and at start.
|
||||
- Bell+Signal: Sound at each minute, at 30s and at 10s countdown.
|
||||
- No indicator: No sound.
|
||||
|
||||
*/
|
||||
|
||||
#define sl_SELECTIONS 6
|
||||
#define DEFAULT_MINUTES { 5,4,1,0,0,0 }
|
||||
|
||||
|
@ -38,30 +70,29 @@ static inline int32_t get_tz_offset(movement_settings_t *settings) {
|
|||
return movement_timezone_offsets[settings->bit.time_zone] * 60;
|
||||
}
|
||||
|
||||
static int lap = 0;
|
||||
bool ringflag = false;
|
||||
int8_t double_beep[] = {BUZZER_NOTE_C8, 4, BUZZER_NOTE_REST, 5, BUZZER_NOTE_C8, 5, 0};
|
||||
int8_t single_beep[] = {BUZZER_NOTE_C8, 4, 0};
|
||||
int8_t long_beep[] = {BUZZER_NOTE_C8, 40, 0};
|
||||
int beepseconds[] = {600, 540, 480, 420, 360, 300, 240, 180, 120, 60, 30, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; //seconds before start that can trigger the buzzer. Every whole minute, 30s before start & 10s countdown.
|
||||
int beepseconds_size = sizeof(beepseconds) / sizeof(int);
|
||||
int beepflag = 0;
|
||||
int alarmflag = 3;
|
||||
|
||||
static void reset(sailing_state_t *state) {
|
||||
state->index = 0;
|
||||
state->mode = sl_waiting;
|
||||
movement_cancel_background_task();
|
||||
watch_clear_indicator(WATCH_INDICATOR_BELL);
|
||||
watch_clear_indicator(WATCH_INDICATOR_LAP);
|
||||
beepflag = 0;
|
||||
ringflag = false;
|
||||
}
|
||||
|
||||
static void start(sailing_state_t *state, movement_settings_t *settings) {
|
||||
uint8_t minutes = state->minutes[state->index];
|
||||
if (minutes == 0) {
|
||||
reset(state);
|
||||
return;
|
||||
}
|
||||
if (state->index < 5) {
|
||||
minutes -= state->minutes[state->index+1];
|
||||
}
|
||||
|
||||
state->mode = sl_running;
|
||||
watch_date_time now = watch_rtc_get_date_time();
|
||||
state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings));
|
||||
state->target_ts = watch_utility_offset_timestamp(state->now_ts, 0, minutes, 0);
|
||||
watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, get_tz_offset(settings));
|
||||
movement_schedule_background_task_for_face(state->watch_face_index, target_dt);
|
||||
watch_set_indicator(WATCH_INDICATOR_BELL);
|
||||
static void counting(sailing_state_t *state) {
|
||||
state->mode = sl_counting;
|
||||
movement_cancel_background_task();
|
||||
watch_set_indicator(WATCH_INDICATOR_LAP);
|
||||
}
|
||||
|
||||
static void draw(sailing_state_t *state, uint8_t subsecond, movement_settings_t *settings) {
|
||||
|
@ -72,36 +103,54 @@ static void draw(sailing_state_t *state, uint8_t subsecond, movement_settings_t
|
|||
|
||||
uint32_t delta;
|
||||
div_t result;
|
||||
uint8_t min, sec;
|
||||
uint8_t add = 0;
|
||||
uint8_t hrs, min, sec;
|
||||
|
||||
switch (state->mode) {
|
||||
case sl_running:
|
||||
if (state->index < 5) {
|
||||
add = state->minutes[state->index+1];
|
||||
}
|
||||
if (state->now_ts >= state->target_ts) {
|
||||
if (state->now_ts > state->target_ts) {
|
||||
delta = 0;
|
||||
counting(state); //in case buttons are pressed while sound is played (esp. in the last 10s), the timing of ring might be thrown off. Beep stops and the switch to counting doesn't occur. temporary fix/safety net.
|
||||
} else {
|
||||
delta = state->target_ts - state->now_ts;
|
||||
}
|
||||
result = div(delta, 60);
|
||||
min = result.quot + add;
|
||||
min = result.quot;
|
||||
sec = result.rem;
|
||||
|
||||
if (min > 0) {
|
||||
sprintf(buf, "SL %2d%02d", min, sec);
|
||||
sprintf(buf, "SA1L %2d%02d", min, sec);
|
||||
} else {
|
||||
sprintf(buf, "SL %2d ", sec);
|
||||
sprintf(buf, "SA1L %2d ", sec);
|
||||
}
|
||||
break;
|
||||
case sl_waiting:
|
||||
sprintf(buf, "SL %2d%02d", state->minutes[0], 0);
|
||||
sprintf(buf, "SA1L %2d%02d", state->minutes[0], 0);
|
||||
break;
|
||||
case sl_setting:
|
||||
// this sprintf to a larger tmp is to guarantee that no buffer overflows
|
||||
// occur here (and to squelch the corresponding compiler warning)
|
||||
sprintf(tmp, "SL %1d%1d%1d%1d%1d%1d",
|
||||
if (state->minutes[0] == 10) { //print 10 vertically.
|
||||
sprintf(tmp, "SA1L %1d%1d%1d%1d%1d",
|
||||
state->minutes[1],
|
||||
state->minutes[2],
|
||||
state->minutes[3],
|
||||
state->minutes[4],
|
||||
state->minutes[5]
|
||||
);
|
||||
memcpy(buf, tmp, sizeof(buf));
|
||||
if (subsecond % 2) {
|
||||
buf[4 + state->selection] = ' ';
|
||||
}
|
||||
watch_display_string(buf, 0);
|
||||
if (!(subsecond % 2) || state->selection != 0) {
|
||||
watch_set_pixel(0, 18);
|
||||
watch_set_pixel(0, 19);
|
||||
watch_set_pixel(1, 18);
|
||||
watch_set_pixel(1, 19);
|
||||
}
|
||||
return;
|
||||
}
|
||||
sprintf(tmp, "SA1L%1d%1d%1d%1d%1d%1d",
|
||||
state->minutes[0],
|
||||
state->minutes[1],
|
||||
state->minutes[2],
|
||||
|
@ -114,29 +163,89 @@ static void draw(sailing_state_t *state, uint8_t subsecond, movement_settings_t
|
|||
buf[4 + state->selection] = ' ';
|
||||
}
|
||||
break;
|
||||
case sl_counting:
|
||||
delta = state->now_ts - state->target_ts;
|
||||
if (state->now_ts <= state->target_ts) {
|
||||
sprintf(buf, "SA1L %2d ", 0);
|
||||
}
|
||||
else {
|
||||
result = div(delta, 3600);
|
||||
hrs = result.quot;
|
||||
delta -= 60*hrs;
|
||||
result = div(delta, 60);
|
||||
min = result.quot;
|
||||
sec = result.rem;
|
||||
sprintf(buf, "SL%2d%2d%02d%02d", lap, hrs, min, sec);//implement counting
|
||||
if (hrs > 23) {
|
||||
reset(state);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
watch_display_string(buf, 0);
|
||||
}
|
||||
|
||||
static void ring(sailing_state_t *state, movement_settings_t *settings) {
|
||||
movement_play_signal();
|
||||
state->index += 1;
|
||||
if (state->index > 5) {
|
||||
reset(state);
|
||||
return;
|
||||
}
|
||||
uint8_t next_min = state->minutes[state->index];
|
||||
if (next_min == 0) {
|
||||
reset(state);
|
||||
return;
|
||||
}
|
||||
static void ring(sailing_state_t *state, movement_settings_t *settings) {
|
||||
// if ring is called in background (while on another face), a button press can interrupt and cancel the execution.
|
||||
// To reduce the probability of cancelling all future alarms, the new alarm is set as soon as possible after calling ring.
|
||||
movement_cancel_background_task();
|
||||
start(state, settings);
|
||||
if (beepflag + 1 == beepseconds_size) { //equivalent to (beepflag + 1 == sizeof(beepseconds) / sizeof(int)) but without needing to divide here => quicker
|
||||
if (alarmflag != 0){
|
||||
watch_buzzer_play_sequence(long_beep, NULL);
|
||||
}
|
||||
movement_cancel_background_task();
|
||||
counting(state);
|
||||
return;
|
||||
}
|
||||
state->nextbeep_ts = state->target_ts - beepseconds[beepflag+1];
|
||||
watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->nextbeep_ts, get_tz_offset(settings));
|
||||
movement_schedule_background_task_for_face(state->watch_face_index, target_dt);
|
||||
//background task is set, now we have time to play the tune. If this is cancelled accidentally, the next alarm will still ring. Sound is implemented non-blocking, so that neither buttons nor display output are compromised.
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (beepseconds[beepflag] == 60 * state->minutes[i]) {
|
||||
if (alarmflag > 1) {
|
||||
watch_buzzer_play_sequence((int8_t *)double_beep, NULL);
|
||||
}
|
||||
ringflag = true;
|
||||
}
|
||||
}
|
||||
if (!ringflag) {
|
||||
if (alarmflag == 3) {
|
||||
watch_buzzer_play_sequence((int8_t *)single_beep, NULL);
|
||||
}
|
||||
}
|
||||
ringflag = false;
|
||||
beepflag++;
|
||||
}
|
||||
|
||||
static void start(sailing_state_t *state, movement_settings_t *settings) {//gets called by starting / switching to next signal
|
||||
while (beepseconds[beepflag] < state->minutes[state->index]*60) {
|
||||
state->index++;
|
||||
}
|
||||
while (beepseconds[beepflag] > state->minutes[state->index]*60) {
|
||||
beepflag++;
|
||||
}
|
||||
if (state->index > 5 || state->minutes[state->index] == 0) {
|
||||
watch_date_time now = watch_rtc_get_date_time();
|
||||
state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings));
|
||||
state->target_ts = state->now_ts;
|
||||
if (alarmflag != 0){
|
||||
watch_buzzer_play_sequence(long_beep, NULL);
|
||||
}
|
||||
counting(state);
|
||||
return;
|
||||
}
|
||||
movement_request_tick_frequency(1); //synchronises tick with the moment the button was pressed. Solves 1s offset between sound and display, solves up to +-0.5s offset between button action and display.
|
||||
state->mode = sl_running;
|
||||
watch_date_time now = watch_rtc_get_date_time();
|
||||
state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings));
|
||||
state->target_ts = watch_utility_offset_timestamp(state->now_ts, 0, state->minutes[state->index], 0);
|
||||
ring(state, settings);
|
||||
}
|
||||
|
||||
static void settings_increment(sailing_state_t *state) {
|
||||
state->minutes[state->selection] += 1;
|
||||
uint8_t max = 10;
|
||||
uint8_t max = 11;
|
||||
if (state->selection > 0) {
|
||||
max = state->minutes[state->selection-1];
|
||||
}
|
||||
|
@ -179,19 +288,41 @@ void sailing_face_activate(movement_settings_t *settings, void *context) {
|
|||
watch_date_time now = watch_rtc_get_date_time();
|
||||
state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings));
|
||||
}
|
||||
if(state->mode == sl_counting) {
|
||||
watch_date_time now = watch_rtc_get_date_time();
|
||||
state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings));
|
||||
watch_set_indicator(WATCH_INDICATOR_LAP);
|
||||
}
|
||||
switch (alarmflag) {
|
||||
case 0: //no sound
|
||||
watch_clear_indicator(WATCH_INDICATOR_BELL);
|
||||
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
break;
|
||||
case 1: //sound at start only
|
||||
watch_set_indicator(WATCH_INDICATOR_BELL);
|
||||
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
break;
|
||||
case 2: //sound at set minutes
|
||||
watch_clear_indicator(WATCH_INDICATOR_BELL);
|
||||
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
break;
|
||||
case 3: //sound at every minute, 30s, 10-0s
|
||||
watch_set_indicator(WATCH_INDICATOR_BELL);
|
||||
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool sailing_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
|
||||
(void) settings;
|
||||
sailing_state_t *state = (sailing_state_t *)context;
|
||||
|
||||
switch (event.event_type) {
|
||||
case EVENT_ACTIVATE:
|
||||
draw(state, event.subsecond, settings);
|
||||
break;
|
||||
case EVENT_TICK:
|
||||
if (state->mode == sl_running) {
|
||||
if (state->mode == sl_running || state->mode == sl_counting) {
|
||||
state->now_ts++;
|
||||
}
|
||||
draw(state, event.subsecond, settings);
|
||||
|
@ -200,6 +331,35 @@ bool sailing_face_loop(movement_event_t event, movement_settings_t *settings, vo
|
|||
if (state->mode == sl_running) {
|
||||
reset(state);
|
||||
}
|
||||
if (state->mode == sl_counting) {
|
||||
reset(state);
|
||||
}
|
||||
if (state->mode == sl_setting) {
|
||||
if (alarmflag == 3) {
|
||||
alarmflag = 0;
|
||||
}
|
||||
else {
|
||||
alarmflag++;
|
||||
}
|
||||
switch (alarmflag) {
|
||||
case 0: //no sound
|
||||
watch_clear_indicator(WATCH_INDICATOR_BELL);
|
||||
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
break;
|
||||
case 1: //sound at start only
|
||||
watch_set_indicator(WATCH_INDICATOR_BELL);
|
||||
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
break;
|
||||
case 2: //sound at set minutes
|
||||
watch_clear_indicator(WATCH_INDICATOR_BELL);
|
||||
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
break;
|
||||
case 3: //sound at every minute, 30s, 10-0s
|
||||
watch_set_indicator(WATCH_INDICATOR_BELL);
|
||||
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EVENT_LIGHT_BUTTON_UP:
|
||||
switch(state->mode) {
|
||||
|
@ -218,21 +378,29 @@ bool sailing_face_loop(movement_event_t event, movement_settings_t *settings, vo
|
|||
movement_request_tick_frequency(1);
|
||||
}
|
||||
break;
|
||||
case sl_counting:
|
||||
movement_illuminate_led();
|
||||
break;
|
||||
}
|
||||
draw(state, event.subsecond, settings);
|
||||
break;
|
||||
case EVENT_ALARM_BUTTON_UP:
|
||||
switch(state->mode) {
|
||||
case sl_running:
|
||||
ring(state, settings);
|
||||
start(state, settings);
|
||||
break;
|
||||
case sl_waiting:
|
||||
movement_play_signal();
|
||||
start(state, settings);
|
||||
break;
|
||||
case sl_setting:
|
||||
settings_increment(state);
|
||||
break;
|
||||
case sl_counting:
|
||||
//implement lap counting up to 39
|
||||
if (lap <39){
|
||||
lap++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
draw(state, event.subsecond, settings);
|
||||
break;
|
||||
|
@ -247,9 +415,12 @@ bool sailing_face_loop(movement_event_t event, movement_settings_t *settings, vo
|
|||
draw(state, event.subsecond, settings);
|
||||
break;
|
||||
}
|
||||
if (state->mode == sl_counting) {
|
||||
lap = 0;
|
||||
}
|
||||
break;
|
||||
case EVENT_TIMEOUT:
|
||||
if (state->mode != sl_running) {
|
||||
if (state->mode != sl_running && state->mode != sl_counting) {
|
||||
movement_move_to_face(0);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Jan H. Voigt
|
||||
* Copyright (c) 2022 Wesley Ellis
|
||||
* Copyright (c) 2022 Niclas Hoyer
|
||||
*
|
||||
|
@ -38,13 +39,15 @@ A sailing sailing/timer face
|
|||
typedef enum {
|
||||
sl_waiting,
|
||||
sl_running,
|
||||
sl_setting
|
||||
sl_setting,
|
||||
sl_counting
|
||||
} sailing_mode_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t watch_face_index;
|
||||
uint32_t target_ts;
|
||||
uint32_t now_ts;
|
||||
uint32_t nextbeep_ts;
|
||||
uint8_t index;
|
||||
uint8_t minutes[6];
|
||||
uint8_t selection;
|
||||
|
|
Loading…
Reference in a new issue