'movement' -> 'legacy' to signal things we still need to bring in

This commit is contained in:
Joey Castillo
2024-11-27 10:56:50 -05:00
parent bc08c5a05e
commit 9719567047
221 changed files with 6 additions and 6 deletions

View File

@@ -1,231 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Ruben Nic
*
* 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 <math.h>
#include "close_enough_clock_face.h"
#include "watch.h"
#include "watch_utility.h"
const char *words[12] = {
" ",
" 5",
"10",
"15",
"20",
"25",
"30",
"35",
"40",
"45",
"50",
"55",
};
static const char *past_word = " P";
static const char *to_word = " 2";
static const char *oclock_word = "OC";
// sets when in the five minute period we switch
// from "X past HH" to "X to HH+1"
static const int hour_switch_index = 8;
static void _update_alarm_indicator(bool settings_alarm_enabled, close_enough_clock_state_t *state) {
state->alarm_enabled = settings_alarm_enabled;
if (state->alarm_enabled) {
watch_set_indicator(WATCH_INDICATOR_BELL);
} else {
watch_clear_indicator(WATCH_INDICATOR_BELL);
};
}
void close_enough_clock_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(close_enough_clock_state_t));
}
}
void close_enough_clock_face_activate(void *context) {
close_enough_clock_state_t *state = (close_enough_clock_state_t *)context;
if (watch_sleep_animation_is_running()) {
watch_stop_sleep_animation();
}
if (movement_clock_mode_24h()) {
watch_set_indicator(WATCH_INDICATOR_24H);
}
// show alarm indicator if there is an active alarm
_update_alarm_indicator(movement_alarm_enabled(), state);
// this ensures that none of the five_minute_periods will match, so we always rerender when the face activates
state->prev_five_minute_period = -1;
state->prev_min_checked = -1;
}
bool close_enough_clock_face_loop(movement_event_t event, void *context) {
close_enough_clock_state_t *state = (close_enough_clock_state_t *)context;
char buf[11];
watch_date_time_t date_time;
bool show_next_hour = false;
int prev_five_minute_period;
int prev_min_checked;
int close_enough_hour;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
date_time = watch_rtc_get_date_time();
prev_five_minute_period = state->prev_five_minute_period;
prev_min_checked = state->prev_min_checked;
// check the battery voltage once a day...
if (date_time.unit.day != state->last_battery_check) {
state->last_battery_check = date_time.unit.day;
watch_enable_adc();
uint16_t voltage = watch_get_vcc_voltage();
watch_disable_adc();
// 2.2 volts will happen when the battery has maybe 5-10% remaining?
// we can refine this later.
state->battery_low = (voltage < 2200);
}
// ...and set the LAP indicator if low.
if (state->battery_low) {
watch_set_indicator(WATCH_INDICATOR_LAP);
}
// same minute, skip update
if (date_time.unit.minute == prev_min_checked) {
break;
} else {
state->prev_min_checked = date_time.unit.minute;
}
int five_minute_period = (date_time.unit.minute / 5) % 12;
// If we are 60% to the next 5 interval, move up to the next period
if (fmodf(date_time.unit.minute / 5.0f, 1.0f) > 0.5f) {
// If we are on the last 5 interval and moving to the next period we need to display the next hour because we are wrapping around
if (five_minute_period == 11) {
show_next_hour = true;
}
five_minute_period = (five_minute_period + 1) % 12;
}
// same five_minute_period, skip update
if (five_minute_period == prev_five_minute_period) {
break;
}
// we don't want to modify date_time.unit.hour just in case other watch faces use it
close_enough_hour = date_time.unit.hour;
// move from "MM(mins) P HH" to "MM(mins) 2 HH+1"
if (five_minute_period >= hour_switch_index || show_next_hour) {
close_enough_hour = (close_enough_hour + 1) % 24;
}
if (!movement_clock_mode_24h()) {
// if we are in 12 hour mode, do some cleanup.
if (close_enough_hour < 12) {
watch_clear_indicator(WATCH_INDICATOR_PM);
} else {
watch_set_indicator(WATCH_INDICATOR_PM);
}
close_enough_hour %= 12;
if (close_enough_hour == 0) {
close_enough_hour = 12;
}
date_time.unit.hour %= 12;
if (date_time.unit.hour == 0) {
date_time.unit.hour = 12;
}
}
char first_word[3];
char second_word[3];
char third_word[3];
if (five_minute_period == 0) { // "HH OC",
sprintf(first_word, "%2d", close_enough_hour);
strncpy(second_word, words[five_minute_period], 3);
strncpy(third_word, oclock_word, 3);
} else {
int words_length = sizeof(words) / sizeof(words[0]);
strncpy(
first_word,
five_minute_period >= hour_switch_index ?
words[words_length - five_minute_period] :
words[five_minute_period],
3
);
strncpy(
second_word,
five_minute_period >= hour_switch_index ?
to_word : past_word,
3
);
sprintf(third_word, "%2d", close_enough_hour);
}
sprintf(
buf,
"%s%2d%s%s%s",
watch_utility_get_weekday(date_time),
date_time.unit.day,
first_word,
second_word,
third_word
);
watch_display_string(buf, 0);
state->prev_five_minute_period = five_minute_period;
// handle alarm indicator
if (state->alarm_enabled != movement_alarm_enabled()) {
_update_alarm_indicator(movement_alarm_enabled(), state);
}
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void close_enough_clock_face_resign(void *context) {
(void) context;
}

View File

@@ -1,62 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Ruben Nic
*
* 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 CLOSE_ENOUGH_CLOCK_FACE_H_
#define CLOSE_ENOUGH_CLOCK_FACE_H_
/*
* CLOSE ENOUGH CLOCK FACE
*
* Displays the current time; but only in periods of 5.
* Just in the in the formats of:
* - "10 past 5"
* - "15 to 7"
* - "6 o'clock"
*
*/
#include "movement.h"
typedef struct {
int prev_five_minute_period;
int prev_min_checked;
uint8_t last_battery_check;
bool battery_low;
bool alarm_enabled;
} close_enough_clock_state_t;
void close_enough_clock_face_setup(uint8_t watch_face_index, void ** context_ptr);
void close_enough_clock_face_activate(void *context);
bool close_enough_clock_face_loop(movement_event_t event, void *context);
void close_enough_clock_face_resign(void *context);
#define close_enough_clock_face ((const watch_face_t){ \
close_enough_clock_face_setup, \
close_enough_clock_face_activate, \
close_enough_clock_face_loop, \
close_enough_clock_face_resign, \
NULL, \
})
#endif // CLOSE_ENOUGH_CLOCK_FACE_H_

View File

@@ -1,136 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Wesley Aptekar-Cassels
*
* 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 <math.h>
#include "day_night_percentage_face.h"
#include "watch_utility.h"
#include "sunriset.h"
// fmod but handle negatives right
static double better_fmod(double x, double y) {
return fmod(fmod(x, y) + y, y);
}
static void recalculate(watch_date_time_t utc_now, day_night_percentage_state_t *state) {
movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1);
if (movement_location.reg == 0) {
state->result = -2;
return;
}
// Weird quirky unsigned things were happening when I tried to cast these directly to doubles below.
// it looks redundant, but extracting them to local int16's seemed to fix it.
int16_t lat_centi = (int16_t)movement_location.bit.latitude;
int16_t lon_centi = (int16_t)movement_location.bit.longitude;
double lat = (double)lat_centi / 100.0;
double lon = (double)lon_centi / 100.0;
state->daylen = day_length(utc_now.unit.year + WATCH_RTC_REFERENCE_YEAR, utc_now.unit.month, utc_now.unit.day, lon, lat);
state->result = sun_rise_set(utc_now.unit.year + WATCH_RTC_REFERENCE_YEAR, utc_now.unit.month, utc_now.unit.day, lon, lat, &state->rise, &state->set);
}
void day_night_percentage_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(day_night_percentage_state_t));
day_night_percentage_state_t *state = (day_night_percentage_state_t *)*context_ptr;
watch_date_time_t utc_now = watch_utility_date_time_convert_zone(watch_rtc_get_date_time(), movement_get_current_timezone_offset(), 0);
recalculate(utc_now, state);
}
}
void day_night_percentage_face_activate(void *context) {
(void) context;
}
bool day_night_percentage_face_loop(movement_event_t event, void *context) {
day_night_percentage_state_t *state = (day_night_percentage_state_t *)context;
char buf[12];
watch_date_time_t date_time = watch_rtc_get_date_time();
watch_date_time_t utc_now = watch_utility_date_time_convert_zone(date_time, movement_get_current_timezone_offset(), 0);
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
if ((utc_now.unit.hour == 0 && utc_now.unit.minute == 0 && utc_now.unit.second == 0) || state->result == -2) {
recalculate(utc_now, state);
}
if (state->result == -2) {
watch_display_string(" no Loc", 0);
break;
}
const char* weekday = watch_utility_get_weekday(date_time);
if (state->result != 0) {
if (state->result == 1) {
watch_clear_indicator(WATCH_INDICATOR_PM);
} else {
watch_set_indicator(WATCH_INDICATOR_PM);
}
sprintf(buf, "%s%2dEtrnal", weekday, date_time.unit.day);
watch_display_string(buf, 0);
} else {
double day_hours_decimal = utc_now.unit.hour + (utc_now.unit.minute + (utc_now.unit.second / 60.0)) / 60.0;
double day_percentage = (24.0 - better_fmod(state->rise - day_hours_decimal, 24.0)) / state->daylen;
double night_percentage = (24.0 - better_fmod(state->set - day_hours_decimal, 24.0)) / (24 - state->daylen);
uint16_t percentage;
if (day_percentage > 0.0 && day_percentage < 1.0) {
percentage = day_percentage * 10000;
watch_clear_indicator(WATCH_INDICATOR_PM);
} else {
percentage = night_percentage * 10000;
watch_set_indicator(WATCH_INDICATOR_PM);
}
if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
if (!watch_sleep_animation_is_running()) watch_start_sleep_animation(500);
sprintf(buf, "%s%2d %02d ", weekday, date_time.unit.day, percentage / 100);
} else {
sprintf(buf, "%s%2d %04d", weekday, date_time.unit.day, percentage);
}
watch_display_string(buf, 0);
}
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void day_night_percentage_face_resign(void *context) {
(void) context;
}

View File

@@ -1,66 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Wesley Aptekar-Cassels
*
* 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 DAY_NIGHT_PERCENTAGE_FACE_H_
#define DAY_NIGHT_PERCENTAGE_FACE_H_
#include "movement.h"
/*
* Day/night percentage face
*
* Shows the percentage of the way through the day/night the current time is.
*
* The time digits show the percentage of the way through the day/night it is,
* with decimals in the smaller seconds digits. If the day or night will last
* for a full 24 hours, the text "Etrnal" is displayed instead of a percentage.
* The "PM" indicator is set when it is currently nighttime. The weekday and
* day digits display the weekday and day, as one would expect.
*
* This face does not currently offer any configuration. You must set the
* location register with some other face.
*/
typedef struct {
int result; // -1, 0, 1: result from sun_rise_set, -2: no location set
double rise;
double set;
double daylen;
} day_night_percentage_state_t;
void day_night_percentage_face_setup(uint8_t watch_face_index, void ** context_ptr);
void day_night_percentage_face_activate(void *context);
bool day_night_percentage_face_loop(movement_event_t event, void *context);
void day_night_percentage_face_resign(void *context);
#define day_night_percentage_face ((const watch_face_t){ \
day_night_percentage_face_setup, \
day_night_percentage_face_activate, \
day_night_percentage_face_loop, \
day_night_percentage_face_resign, \
NULL, \
})
#endif // DAY_NIGHT_PERCENTAGE_FACE_H_

View File

@@ -1,176 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Curtis J. Brown <mrbrown8@juno.com>
*
* 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 "decimal_time_face.h"
#include "watch.h"
void decimal_time_face_setup(uint8_t watch_face_index, void ** context_ptr) {
// These next two lines just silence the compiler warnings associated with unused parameters.
// We have no use for the settings or the watch_face_index, so we make that explicit here.
(void) watch_face_index;
(void) context_ptr;
// At boot, context_ptr will be NULL indicating that we don't have anyplace to store our context.
if (*context_ptr == NULL) {
// in this case, we allocate an area of memory sufficient to store the stuff we need to track.
*context_ptr = malloc(sizeof(decimal_time_face_state_t));
decimal_time_face_state_t *state = (decimal_time_face_state_t *)*context_ptr;
state->chime_enabled = false;
state->features_to_show = 0 ;
}
}
void decimal_time_face_activate(void *context) {
// same as above: silence the warning, we don't need to check the settings.
// we do however need to set some things in our context. Here we cast it to the correct type...
decimal_time_face_state_t *state = (decimal_time_face_state_t *)context;
watch_set_indicator(WATCH_INDICATOR_24H); // This face is always 24h, so just set the indicators
watch_clear_indicator(WATCH_INDICATOR_PM);
if (state->chime_enabled) {
watch_set_indicator(WATCH_INDICATOR_BELL);
}
}
bool decimal_time_face_loop(movement_event_t event, void *context) {
decimal_time_face_state_t *state = (decimal_time_face_state_t *)context;
char buf[16];
uint8_t centihours, decimal_seconds;
watch_date_time_t date_time;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
// on activate and tick
date_time = watch_rtc_get_date_time();
centihours = (( date_time.unit.minute * 60 + date_time.unit.second ) * 100 ) / 3600; // Integer division, fractions get dropped, no need for abs() (bonus)
decimal_seconds = ( date_time.unit.minute * 60 + date_time.unit.second ) % 36 ;
switch (state->features_to_show) {
case 0:
sprintf( buf, "dT %02d%02d ", date_time.unit.hour, centihours );
break;
case 1:
sprintf( buf, "dT %02d%02d%2d", date_time.unit.hour, centihours, decimal_seconds );
break;
case 2:
sprintf( buf, "dT%2d%02d%02d ", date_time.unit.day, date_time.unit.hour, centihours );
break;
case 3:
sprintf( buf, "dT%2d%02d%02d%2d", date_time.unit.day, date_time.unit.hour, centihours, decimal_seconds );
break;
}
watch_display_string(buf, 0); // display calculated time
// at the top of every hour...
if (date_time.unit.minute == 0 && date_time.unit.second == 0 && state->chime_enabled) {
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
watch_buzzer_play_note(BUZZER_NOTE_E6, 75);
watch_buzzer_play_note(BUZZER_NOTE_REST, 15);
watch_buzzer_play_note(BUZZER_NOTE_E6, 75);
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
}
break;
case EVENT_ALARM_LONG_PRESS:
state->chime_enabled = !state->chime_enabled; // just like from simple_watch_face
if (state->chime_enabled)
watch_set_indicator(WATCH_INDICATOR_BELL);
else
watch_clear_indicator(WATCH_INDICATOR_BELL);
break;
case EVENT_ALARM_BUTTON_UP:
state->features_to_show += 1 ; // cycle thru what's to be shown
break;
case EVENT_LOW_ENERGY_UPDATE:
// This low energy mode update occurs once a minute, if the watch face is in the
// foreground when Movement enters low energy mode. We have the option of supporting
// this mode, but since our watch face animates once a second, the "Hello there" face
// isn't very useful in this mode. So we choose not to support it. (continued below)
break;
case EVENT_TIMEOUT:
// ... Instead, we respond to the timeout event. This event happens after a configurable
// interval on screen (1-30 minutes). The watch will give us this event as a chance to
// resign control if we want to, and in this case, we do.
// This function will return the watch to the first screen (usually a simple clock),
// and it will do it long before the watch enters low energy mode. This ensures we
// won't be on screen, and thus opts us out of getting the EVENT_LOW_ENERGY_UPDATE above.
//movement_move_to_face(0);
break;
default:
movement_default_loop_handler(event);
break;
}
return true;
}
void decimal_time_face_resign(void *context) {
// our watch face, like most watch faces, has nothing special to do when resigning.
// there are no peripherals or sensors here to worry about turning off.
(void) context;
}
// void decimal_time_face_advise() {
//
// }

View File

@@ -1,71 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Curtis J. Brown <mrbrown8@juno.com>
*
* 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 DECIMAL_TIME_FACE_H_
#define DECIMAL_TIME_FACE_H_
/*
* DECIMAL TIME face
*
* This face presents the current time as hours and hundredths of an hour. Every hundreth of an hour, or "centihour",
* occurs every 36 seconds. Because they range from 0 to 99, centihours, in the seventies range, will be displayed with a lowercase 7.
*
* See https://en.wikipedia.org/wiki/Decimal_time#Decimal_hours
*
* This method of timekeeping is used by the United States Postal Service.
* http://www.branch38nalc.com/sitebuildercontent/sitebuilderfiles/CONVERSION_TABLE_TIME.pdf
* https://postalemployeenetwork.com/time-conversion-print.htm
*
* This method may be used by other organizations as well
* https://www.labor.nc.gov/workplace-rights/employer-responsibilities/time-conversion-chart-minutes-decimal-hours
* https://uh.edu/office-of-finance/payroll/time_conversion_chart_minutes_to_decimalhours.pdf
* https://www.placer.ca.gov/DocumentCenter/View/3860/Decimals-to-Minutes-Conversion-Table-PDF
* https://hr.colostate.edu/minute-to-decimal-conversion-chart/
*
* Many thanks go to Joey Castillo for making this project happen.
*/
#include "movement.h"
typedef struct {
bool chime_enabled; // did the user enable hourly chime for this face?
uint8_t features_to_show : 2 ; // what features are to be displayed?
} decimal_time_face_state_t;
void decimal_time_face_setup(uint8_t watch_face_index, void ** context_ptr);
void decimal_time_face_activate(void *context);
bool decimal_time_face_loop(movement_event_t event, void *context);
void decimal_time_face_resign(void *context);
// void decimal_time_face_advise();
#define decimal_time_face ((const watch_face_t){ \
decimal_time_face_setup, \
decimal_time_face_activate, \
decimal_time_face_loop, \
decimal_time_face_resign, \
NULL, \
})
#endif // DECIMAL_TIME_FACE_H_

View File

@@ -1,242 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 CarpeNoctem
*
* 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 "french_revolutionary_face.h"
void french_revolutionary_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(french_revolutionary_state_t));
memset(*context_ptr, 0, sizeof(french_revolutionary_state_t));
// Do any one-time tasks in here; the inside of this conditional happens only at boot.
french_revolutionary_state_t *state = (french_revolutionary_state_t *)*context_ptr;
state->use_am_pm = false;
state->show_seconds = true;
state->display_type = 0;
state->colon_set_after_splash = false;
}
// Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.
}
void french_revolutionary_face_activate(void *context) {
french_revolutionary_state_t *state = (french_revolutionary_state_t *)context;
// Handle any tasks related to your watch face coming on screen.
state->colon_set_after_splash = false;
}
bool french_revolutionary_face_loop(movement_event_t event, void *context) {
french_revolutionary_state_t *state = (french_revolutionary_state_t *)context;
char buf[11];
watch_date_time_t date_time;
fr_decimal_time decimal_time;
switch (event.event_type) {
case EVENT_ACTIVATE:
// Initial UI - Show a quick "splash screen"
watch_clear_display();
watch_display_string("FR dECimL", 0);
break;
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
date_time = watch_rtc_get_date_time();
decimal_time = get_decimal_time(&date_time);
set_display_buffer(buf, state, &decimal_time, &date_time);
// If we're in low-energy mode, don't write out the seconds. Also start the LE tick animation if it's not already going.
if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
buf[8] = ' ';
buf[9] = ' ';
if (!watch_sleep_animation_is_running()) { watch_start_sleep_animation(500); }
}
// Update the display with our decimal time
watch_display_string(buf, 0);
// Oh, and a one-off to set the colon after the "splash screen"
if (!state->colon_set_after_splash) {
watch_set_colon();
state->colon_set_after_splash = true;
}
break;
case EVENT_ALARM_BUTTON_UP:
state->display_type += 1 ; // cycle through the display types
if (state->display_type > 2) { state->display_type = 0; } // but return to 0 after 2
break;
case EVENT_ALARM_LONG_PRESS:
// I originally had chiming on the decimal-hour enabled, and this would enable/disable that chime, just like on
// the simple clock and decimal time faces. But because decimal seconds don't always line up with normal seconds,
// I assume the (decimal-)hourly chime could sometimes be missed. Additionally, I need this button for other purposes,
// now that I added seconds on/off toggle and upper normal-time with the ability to toggle that between 12/24hr format.
state->show_seconds = !state->show_seconds;
if (!state->show_seconds) { watch_display_string(" ", 8); }
else { watch_display_string("--", 8); }
break;
case EVENT_LIGHT_LONG_PRESS:
// In case anyone really wants that upper time in 12-hour format. I thought about using the global setting (movement_clock_mode_24h())
// for this preference, but thought someone who prefers 12-hour format normally, might prefer 24hr when compared to a 10hr decimal day,
// so this is separate for now.
state->use_am_pm = !state->use_am_pm;
if (state->use_am_pm) {
watch_clear_indicator(WATCH_INDICATOR_24H);
date_time = watch_rtc_get_date_time();
if (date_time.unit.hour < 12) { watch_clear_indicator(WATCH_INDICATOR_PM); }
else { watch_set_indicator(WATCH_INDICATOR_PM); }
} else {
watch_clear_indicator(WATCH_INDICATOR_PM);
watch_set_indicator(WATCH_INDICATOR_24H);
}
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);
}
// 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 french_revolutionary_face_resign(void *context) {
(void) context;
// handle any cleanup before your watch face goes off-screen.
}
// Calculate decimal time from normal (24hr) time
fr_decimal_time get_decimal_time(watch_date_time_t *date_time) {
uint32_t current_24hr_secs, current_decimal_seconds;
fr_decimal_time decimal_time;
// Current 24-hr time in seconds (There are 86400 of these in a day.)
current_24hr_secs = date_time->unit.hour * 3600 + date_time->unit.minute * 60 + date_time->unit.second;
// Current Decimal Time in seconds. There are 100000 seconds in a 10-hr decimal-time day.
// current_decimal_seconds = current_24hr_seconds * 100000 / 86400, or = current_24_seconds * 1000 / 864;
// By chopping the extra zeros off the end, we can use uint32 instead of uint64.
current_decimal_seconds = current_24hr_secs * 1000 / 864;
decimal_time.hour = current_decimal_seconds / 10000;
// Remove the hours from total seconds and keep the remainder for below.
current_decimal_seconds = current_decimal_seconds - decimal_time.hour * 10000;
decimal_time.minute = current_decimal_seconds / 100;
// Remove the minutes from total seconds and keep the remaining seconds
// Note: I think I used an extra seconds variable here because sprintf or movement weren't liking a uint32...
decimal_time.second = current_decimal_seconds - decimal_time.minute * 100;
return decimal_time;
}
// Fills in the display buffer, depending on the currently-selected display option (and sub-options):
// - Decimal-time only
// - Decimal-time with date in top-right
// - Decimal-time with normal time in the top (minutes first, then hours, due to display limitations)
// TODO: There is some power-saving stuff that simple clock does here around not redrawing characters that haven't changed, but we're not doing that here.
// I'll try to add that optimization could be added in a future commit.
void set_display_buffer(char *buf, french_revolutionary_state_t *state, fr_decimal_time *decimal_time, watch_date_time_t *date_time) {
switch (state->display_type) {
// Decimal time only
case 0:
// Originally I had the day slot set to "FR" (French Revolutionary time), but my brain kept thinking "Friday" whenever I saw it,
// so I changed it to dT (Decimal Time) to avoid that confusion. Apologies to anyone who has the other decimal_time face and this one
// installed concurrently. Maybe the splash screen will help a little.
sprintf( buf, "dT %2d%02d%02d", decimal_time->hour, decimal_time->minute, decimal_time->second );
watch_clear_indicator(WATCH_INDICATOR_PM);
watch_clear_indicator(WATCH_INDICATOR_24H);
break;
// Decimal time and date
case 1:
sprintf( buf, "dT%2d%2d%02d%02d", date_time->unit.day, decimal_time->hour, decimal_time->minute, decimal_time->second );
watch_clear_indicator(WATCH_INDICATOR_PM);
watch_clear_indicator(WATCH_INDICATOR_24H);
break;
// Decimal time on bottom, normal time above
case 2:
if (state->use_am_pm) {
// if we are in 12 hour mode, do some cleanup.
watch_clear_indicator(WATCH_INDICATOR_24H);
if (date_time->unit.hour < 12) {
watch_clear_indicator(WATCH_INDICATOR_PM);
} else {
watch_set_indicator(WATCH_INDICATOR_PM);
}
date_time->unit.hour %= 12;
if (date_time->unit.hour == 0) date_time->unit.hour = 12;
} else {
watch_clear_indicator(WATCH_INDICATOR_PM);
watch_set_indicator(WATCH_INDICATOR_24H);
}
// Note, the date digits don't display a leading zero well, so we don't use it.
sprintf( buf, "%02d%2d%2d%02d%02d", date_time->unit.minute, date_time->unit.hour, decimal_time->hour, decimal_time->minute, decimal_time->second );
// Make the second character of the Day area more readable
buf[1] = fix_character_one(buf[1]);
break;
}
// Finally, if show_seconds is disabled, trim those off.
if (!state->show_seconds) {
buf[8] = ' ';
buf[9] = ' ';
}
}
// Sadly, the second character of the Day field cannot show all numbers, so we make some replacements.
// See https://www.sensorwatch.net/docs/wig/display/#limitations-of-the-weekday-digits
char fix_character_one(char digit) {
char return_char = digit; // We don't need to update this for 0, 1, 3, 7 and 8.
switch(digit) {
case '2':
// Roman numeral / tally representation of 2
return_char = '|'; // Thanks, Joey, for already having this in the character set.
break;
case '4':
// Looks almost like a 4 - just missing the top-left segment.
// 0b01000110
return_char = '&'; // Slight hack - I want 0b01000110, but 0b01000100 is already in the character set and will do, since B and C segments are linked in this position.
break;
case '5':
return_char = 'F'; // F for Five
break;
case '6':
return_char = 'E'; // Looks almost like a 6 - just missing the bottom-right segment. Not super happy with it, but liked it best of the options I tried.
break;
case '9':
return_char = 'N'; // N for Nine
break;
}
return return_char;
}

View File

@@ -1,84 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 CarpeNoctem
*
* 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 FRENCH_REVOLUTIONARY_FACE_H_
#define FRENCH_REVOLUTIONARY_FACE_H_
#include "movement.h"
/*
* French Revolutionary Decimal Time
*
* Similar to the Decimal Time face, but with the day divided into ten hours instead of twenty four.
* Each hour is divided into one hundred minutes, and those minutes are divided into 100 seconds.
* I came across this one the Svalbard watch site here: https://svalbard.watch/pages/about_decimal_time.html
* More info here as well: https://en.wikipedia.org/wiki/Decimal_time
*
* By default, the face just displays the current decimal time. Pressing the alarm button will toggle through other display options:
* 1) Just decimal time (with dT indicator at top)
* 2) Decimal time, with dT indicator and date above.
* 3) Decimal time, with 24-hr time above (where Day and Date would normally be displayed), BUT minutes first then hours.
* Sadly, the first character of the date area only goes up to 3 (see https://www.sensorwatch.net/docs/wig/display/#the-day-digits)
* I was going to begrudgindly leave this display option out when I realized that, but thought it would be better to have this backwards
* representation of the "normal" time than not at all.
*
* A long-press of the light button will toggle the upper time between 12-hr AM/PM and 24-hr mode. I thought of reading the main setting for this,
* but thought that a person could normally prefer 12hr time, but next to a 10hr day want to see the normal time in the 24hr format.
*
* A long-press of the alarm button will toggle the seconds off and on.
*
*/
typedef struct {
bool use_am_pm; // Use 12-hr AM/PM for upper display instead of 24-hr? (Default is 24-hr)
bool show_seconds;
bool colon_set_after_splash;
uint8_t display_type : 2;
} french_revolutionary_state_t;
typedef struct {
uint8_t second : 8; // 0-99
uint8_t minute : 8; // 0-99
uint8_t hour : 5; // 0-10
} fr_decimal_time;
void french_revolutionary_face_setup(uint8_t watch_face_index, void ** context_ptr);
void french_revolutionary_face_activate(void *context);
bool french_revolutionary_face_loop(movement_event_t event, void *context);
void french_revolutionary_face_resign(void *context);
char fix_character_one(char digit);
fr_decimal_time get_decimal_time(watch_date_time_t *date_time);
void set_display_buffer(char *buf, french_revolutionary_state_t *state, fr_decimal_time *decimal_time, watch_date_time_t *date_time);
#define french_revolutionary_face ((const watch_face_t){ \
french_revolutionary_face_setup, \
french_revolutionary_face_activate, \
french_revolutionary_face_loop, \
french_revolutionary_face_resign, \
NULL, \
})
#endif // FRENCH_REVOLUTIONARY_FACE_H_

View File

@@ -1,163 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2022 Joey Castillo
*
* 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 <math.h>
#include "watch_utility.h"
#include "mars_time_face.h"
// note: lander coordinates come from Mars24's `marslandmarks.xml` file
static double site_longitudes[MARS_TIME_NUM_SITES] = {
0, // Mars Coordinated Time, at the meridian
360.0 - 109.9, // Zhurong lander site
360.0 - 77.45088572, // Perseverance lander site
360.0 - 135.623447, // InSight lander site
360.0 - 137.441635, // Curiosity lander site
};
static char site_names[MARS_TIME_NUM_SITES][3] = {
"MC",
"ZH",
"PE",
"IN",
"CU",
};
static uint16_t landing_sols[MARS_TIME_NUM_SITES] = {
0,
52387,
52304,
51511,
49269,
};
typedef struct {
uint8_t hour;
uint8_t minute;
uint8_t second;
} mars_clock_hms_t;
static void _h_to_hms(mars_clock_hms_t *date_time, double h) {
unsigned int seconds = (unsigned int)(h * 3600.0);
date_time->hour = seconds / 3600;
seconds = seconds % 3600;
date_time->minute = floor(seconds / 60);
date_time->second = round(seconds % 60);
}
static void _update(mars_time_state_t *state) {
char buf[11];
watch_date_time_t date_time = watch_rtc_get_date_time();
uint32_t now = watch_utility_date_time_to_unix_time(date_time, movement_get_current_timezone_offset());
// TODO: I'm skipping over some steps here.
// https://www.giss.nasa.gov/tools/mars24/help/algorithm.html
double jdut = 2440587.5 + ((double)now / 86400.0);
double jdtt = jdut + ((37.0 + 32.184) / 86400.0);
double jd2k = jdtt - 2451545.0;
double msd = ((jd2k - 4.5) / 1.0274912517) + 44796.0 - 0.0009626;
double mtc = fmod(24 * msd, 24);
double lmt;
if (state->current_site == 0) {
lmt = mtc;
} else {
double longitude = site_longitudes[state->current_site];
double lmst = mtc - ((longitude * 24.0) / 360.0);
lmt = fmod(lmst + 24, 24);
}
if (state->displaying_sol) {
// TODO: this is not right, mission sol should turn over at midnight local time?
uint16_t sol = floor(msd) - landing_sols[state->current_site];
if (sol < 1000) sprintf(&buf[0], "%s Sol%3d", site_names[state->current_site], sol);
else sprintf(&buf[0], "%s $%6d", site_names[state->current_site], sol);
watch_clear_colon();
watch_clear_indicator(WATCH_INDICATOR_24H);
} else {
mars_clock_hms_t mars_time;
_h_to_hms(&mars_time, lmt);
sprintf(&buf[0], "%s %02d%02d%02d", site_names[state->current_site], mars_time.hour, mars_time.minute, mars_time.second);
watch_set_colon();
watch_set_indicator(WATCH_INDICATOR_24H);
}
watch_display_string(buf, 0);
}
void mars_time_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(mars_time_state_t));
memset(*context_ptr, 0, sizeof(mars_time_state_t));
}
}
void mars_time_face_activate(void *context) {
mars_time_state_t *state = (mars_time_state_t *)context;
(void) state;
}
bool mars_time_face_loop(movement_event_t event, void *context) {
mars_time_state_t *state = (mars_time_state_t *)context;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
_update(state);
break;
case EVENT_LIGHT_BUTTON_UP:
state->displaying_sol = !state->displaying_sol;
_update(state);
break;
case EVENT_LIGHT_LONG_PRESS:
movement_illuminate_led();
break;
case EVENT_ALARM_BUTTON_UP:
state->current_site = (state->current_site + 1) % MARS_TIME_NUM_SITES;
_update(state);
break;
case EVENT_TIMEOUT:
// TODO: make this lower power so we can avoid timeout
movement_move_to_face(0);
break;
case EVENT_LOW_ENERGY_UPDATE:
// TODO: low energy update
// watch_start_sleep_animation(500);
break;
case EVENT_LIGHT_BUTTON_DOWN:
// don't light up every time light is hit
break;
default:
movement_default_loop_handler(event);
break;
}
return true;
}
void mars_time_face_resign(void *context) {
(void) context;
}

View File

@@ -1,84 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2022 Joey Castillo
*
* 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 MARS_TIME_FACE_H_
#define MARS_TIME_FACE_H_
/*
* MARS TIME face
*
* This watch face is dedicated to Martian timekeeping.
* It has several modes, and can display either a time or a date.
*
* Pressing the ALARM button cycles through different time zones on Mars:
* MC - Mars Coordinated Time, the time at Airy-0 Crater on the Martian prime meridian
* ZH - Local mean solar time for the Zhurong rover
* PE - LMST for the Perseverance rover
* IN - LMST for the Insight lander
* CU - LMST for the Curiosity rover
*
* Press the LIGHT button to toggle between displaying time and date:
* MC S - the Mars Sol Date, Martian days since December 29, 1873
* ZH Sol - Mission sol for the Zhurong rover
* PE Sol - Mission sol for the Perseverance rover
* IN S - Mission sol for the InSight lander
* CU S - Mission sol for the Curiosity rover
*
* Note that where the mission sol is below 1000, this watch face displays
* the word “Sol” on the bottom line. When the mission sol is over 1000, the
* word “Sol” will not fit and so it displays a stylized letter S at the top
* right.
*/
#include "movement.h"
typedef enum {
MARS_TIME_MERIDIAN,
MARS_TIME_ZHURONG_SITE,
MARS_TIME_PERSEVERANCE_SITE,
MARS_TIME_INSIGHT_SITE,
MARS_TIME_CURIOSITY_SITE,
MARS_TIME_NUM_SITES,
} mars_time_site_t;
typedef struct {
mars_time_site_t current_site;
bool displaying_sol;
} mars_time_state_t;
void mars_time_face_setup(uint8_t watch_face_index, void ** context_ptr);
void mars_time_face_activate(void *context);
bool mars_time_face_loop(movement_event_t event, void *context);
void mars_time_face_resign(void *context);
#define mars_time_face ((const watch_face_t){ \
mars_time_face_setup, \
mars_time_face_activate, \
mars_time_face_loop, \
mars_time_face_resign, \
NULL, \
})
#endif // MARS_TIME_FACE_H_

View File

@@ -1,114 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Dennisman219
*
* 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 "minimal_clock_face.h"
static void _minimal_clock_face_update_display() {
watch_date_time_t date_time = watch_rtc_get_date_time();
char buffer[11];
if (!movement_clock_mode_24h()) {
date_time.unit.hour %= 12;
sprintf(buffer, "%2d%02d ", date_time.unit.hour, date_time.unit.minute);
} else {
sprintf(buffer, "%02d%02d ", date_time.unit.hour, date_time.unit.minute);
}
watch_display_string(buffer, 4);
}
void minimal_clock_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(minimal_clock_state_t));
memset(*context_ptr, 0, sizeof(minimal_clock_state_t));
// Do any one-time tasks in here; the inside of this conditional happens only at boot.
}
// Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.
}
void minimal_clock_face_activate(void *context) {
(void) context;
// Handle any tasks related to your watch face coming on screen.
watch_set_colon();
}
bool minimal_clock_face_loop(movement_event_t event, void *context) {
(void) context;
switch (event.event_type) {
case EVENT_ACTIVATE:
// Show your initial UI here.
_minimal_clock_face_update_display();
break;
case EVENT_TICK:
// If needed, update your display here.
_minimal_clock_face_update_display();
break;
case EVENT_LIGHT_BUTTON_UP:
// You can use the Light button for your own purposes. Note that by default, Movement will also
// illuminate the LED in response to EVENT_LIGHT_BUTTON_DOWN; to suppress that behavior, add an
// empty case for EVENT_LIGHT_BUTTON_DOWN.
break;
case EVENT_ALARM_BUTTON_UP:
// Just in case you have need for another button.
break;
case EVENT_TIMEOUT:
// Your watch face will receive this event after a period of inactivity. If it makes sense to resign,
// you may uncomment this line to move back to the first watch face in the list:
// 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_sleep_animation(500);
_minimal_clock_face_update_display();
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);
}
// 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 minimal_clock_face_resign(void *context) {
(void) context;
// handle any cleanup before your watch face goes off-screen.
}

View File

@@ -1,57 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Dennisman219
*
* 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 MINIMAL_CLOCK_FACE_H_
#define MINIMAL_CLOCK_FACE_H_
#include "movement.h"
/*
* MINIMAL CLOCK FACE
*
* A minimal clock face that just shows hours and minutes.
* There is nothing to configure. The face follows the 12h/24h setting
*
*/
typedef struct {
// Anything you need to keep track of, put it here!
uint8_t unused;
} minimal_clock_state_t;
void minimal_clock_face_setup(uint8_t watch_face_index, void ** context_ptr);
void minimal_clock_face_activate(void *context);
bool minimal_clock_face_loop(movement_event_t event, void *context);
void minimal_clock_face_resign(void *context);
#define minimal_clock_face ((const watch_face_t){ \
minimal_clock_face_setup, \
minimal_clock_face_activate, \
minimal_clock_face_loop, \
minimal_clock_face_resign, \
NULL, \
})
#endif // MINIMAL_CLOCK_FACE_H_

View File

@@ -1,238 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Jonas Termeau - original repetition_minute_face
* Copyright (c) 2023 Brian Blakley - modified minute_repeater_decimal_face
*
* 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.
*/
/*
* This face, minute_repeater_decimal_face, is a modification of the original
* repetition_minute_face by Jonas Termeau.
*
* This version was created by BrianBinFL to use a decimal minute repeater pattern
* (hours, tens, and minutes) instead of the traditional pattern (hours, quarters,
* minutes).
*
* Also 500ms delays were added after the hours segment and after the tens segment
* to make it easier for the user to realize that the counting for the current
* segment has ended.
*
*/
#include <stdlib.h>
#include "minute_repeater_decimal_face.h"
#include "watch.h"
#include "watch_utility.h"
#include "watch_private_display.h"
void mrd_play_hour_chime(void) {
watch_buzzer_play_note(BUZZER_NOTE_C6, 75);
watch_buzzer_play_note(BUZZER_NOTE_REST, 500);
}
void mrd_play_tens_chime(void) {
watch_buzzer_play_note(BUZZER_NOTE_E6, 75);
watch_buzzer_play_note(BUZZER_NOTE_REST, 150);
watch_buzzer_play_note(BUZZER_NOTE_C6, 75);
watch_buzzer_play_note(BUZZER_NOTE_REST, 750);
}
void mrd_play_minute_chime(void) {
watch_buzzer_play_note(BUZZER_NOTE_E6, 75);
watch_buzzer_play_note(BUZZER_NOTE_REST, 500);
}
static void _update_alarm_indicator(bool settings_alarm_enabled, minute_repeater_decimal_state_t *state) {
state->alarm_enabled = settings_alarm_enabled;
if (state->alarm_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL);
else watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
}
void minute_repeater_decimal_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(minute_repeater_decimal_state_t));
minute_repeater_decimal_state_t *state = (minute_repeater_decimal_state_t *)*context_ptr;
state->signal_enabled = false;
state->watch_face_index = watch_face_index;
}
}
void minute_repeater_decimal_face_activate(void *context) {
minute_repeater_decimal_state_t *state = (minute_repeater_decimal_state_t *)context;
if (watch_sleep_animation_is_running()) watch_stop_sleep_animation();
if (movement_clock_mode_24h()) watch_set_indicator(WATCH_INDICATOR_24H);
// handle chime indicator
if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL);
else watch_clear_indicator(WATCH_INDICATOR_BELL);
// show alarm indicator if there is an active alarm
_update_alarm_indicator(movement_alarm_enabled(), state);
watch_set_colon();
// this ensures that none of the timestamp fields will match, so we can re-render them all.
state->previous_date_time = 0xFFFFFFFF;
}
bool minute_repeater_decimal_face_loop(movement_event_t event, void *context) {
minute_repeater_decimal_state_t *state = (minute_repeater_decimal_state_t *)context;
char buf[11];
uint8_t pos;
watch_date_time_t date_time;
uint32_t previous_date_time;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
date_time = watch_rtc_get_date_time();
previous_date_time = state->previous_date_time;
state->previous_date_time = date_time.reg;
// check the battery voltage once a day...
if (date_time.unit.day != state->last_battery_check) {
state->last_battery_check = date_time.unit.day;
watch_enable_adc();
uint16_t voltage = watch_get_vcc_voltage();
watch_disable_adc();
// 2.2 volts will happen when the battery has maybe 5-10% remaining?
// we can refine this later.
state->battery_low = (voltage < 2200);
}
// ...and set the LAP indicator if low.
if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP);
if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
// everything before seconds is the same, don't waste cycles setting those segments.
watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8);
watch_display_character_lp_seconds('0' + date_time.unit.second % 10, 9);
break;
} else if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
// everything before minutes is the same.
pos = 6;
sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second);
} else {
// other stuff changed; let's do it all.
if (!movement_clock_mode_24h()) {
// if we are in 12 hour mode, do some cleanup.
if (date_time.unit.hour < 12) {
watch_clear_indicator(WATCH_INDICATOR_PM);
} else {
watch_set_indicator(WATCH_INDICATOR_PM);
}
date_time.unit.hour %= 12;
if (date_time.unit.hour == 0) date_time.unit.hour = 12;
}
pos = 0;
if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
if (!watch_sleep_animation_is_running()) watch_start_sleep_animation(500);
sprintf(buf, "%s%2d%2d%02d ", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute);
} else {
sprintf(buf, "%s%2d%2d%02d%02d", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second);
}
}
watch_display_string(buf, pos);
// handle alarm indicator
if (state->alarm_enabled != movement_alarm_enabled()) _update_alarm_indicator(movement_alarm_enabled(), state);
break;
case EVENT_ALARM_LONG_PRESS:
state->signal_enabled = !state->signal_enabled;
if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL);
else watch_clear_indicator(WATCH_INDICATOR_BELL);
break;
case EVENT_BACKGROUND_TASK:
movement_play_signal();
break;
case EVENT_LIGHT_LONG_UP:
/*
* Howdy neighbors, this is the actual complication. Like an actual
* (very expensive) watch with a repetition minute complication it's
* boring at 00:00 or 1:00 and very quite musical at 23:59 or 12:59.
*/
date_time = watch_rtc_get_date_time();
int hours = date_time.unit.hour;
int tens = date_time.unit.minute / 10;
int minutes = date_time.unit.minute % 10;
// chiming hours
if (!movement_clock_mode_24h()) {
hours = date_time.unit.hour % 12;
if (hours == 0) hours = 12;
}
if (hours > 0) {
int count = 0;
for(count = hours; count > 0; --count) {
mrd_play_hour_chime();
}
// do a little pause before proceeding to tens
watch_buzzer_play_note(BUZZER_NOTE_REST, 500);
}
// chiming tens (if needed)
if (tens > 0) {
int count = 0;
for(count = tens; count > 0; --count) {
mrd_play_tens_chime();
}
// do a little pause before proceeding to minutes
watch_buzzer_play_note(BUZZER_NOTE_REST, 500);
}
// chiming minutes (if needed)
if (minutes > 0) {
int count = 0;
for(count = minutes; count > 0; --count) {
mrd_play_minute_chime();
}
}
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void minute_repeater_decimal_face_resign(void *context) {
(void) context;
}
movement_watch_face_advisory_t minute_repeater_decimal_face_advise(void *context) {
minute_repeater_decimal_state_t *state = (minute_repeater_decimal_state_t *)context;
movement_watch_face_advisory_t retval = { 0 };
if (state->signal_enabled) {
watch_date_time_t date_time = watch_rtc_get_date_time();
retval.wants_background_task = date_time.unit.minute == 0;
}
return retval;
}

View File

@@ -1,84 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Jonas Termeau - original repetition_minute_face
* Copyright (c) 2023 Brian Blakley - modified minute_repeater_decimal_face
*
* 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 MINUTE_REPEATER_DECIMAL_FACE_H_
#define MINUTE_REPEATER_DECIMAL_FACE_H_
#include "movement.h"
/*
* A hopefully useful complication for friendly neighbors in the dark
*
* Originating from 1676 from reverend and mechanician Edward Barlow, and
* perfected in 1820 by neighbor Abraham Breguet, a minute repeater or
* "repetition minute" is a complication in a mechanical watch or clock that
* chimes the hours and often minutes at the press of a button. There are many
* types of repeater, from the simple repeater which merely strikes the number
* of hours, to the minute repeater which chimes the time down to the minute,
* using separate tones for hours, decimal hours, and minutes. They originated
* before widespread artificial illumination, to allow the time to be determined
* in the dark, and were also used by the visually impaired.
*
*
* How to use it :
*
* Long press the light button to get an auditive reading of the time like so :
* 0..23 (1..12 if 24-hours format isn't enabled) low beep(s) for the hours
* 0..9 low-high couple pitched beeps for the tens of minutes
* 0..9 high pitched beep(s) for the remaining minutes (ones of minutes)
*
* Prerequisite : a watch with a working buzzer
*
* ~ Only in the darkness can you see the stars. - Martin Luther King ~
*
*/
typedef struct {
uint32_t previous_date_time;
uint8_t last_battery_check;
uint8_t watch_face_index;
bool signal_enabled;
bool battery_low;
bool alarm_enabled;
} minute_repeater_decimal_state_t;
void mrd_play_hour_chime(void);
void mrd_play_tens_chime(void);
void mrd_play_minute_chime(void);
void minute_repeater_decimal_face_setup(uint8_t watch_face_index, void ** context_ptr);
void minute_repeater_decimal_face_activate(void *context);
bool minute_repeater_decimal_face_loop(movement_event_t event, void *context);
void minute_repeater_decimal_face_resign(void *context);
movement_watch_face_advisory_t minute_repeater_decimal_face_advise(void *context);
#define minute_repeater_decimal_face ((const watch_face_t){ \
minute_repeater_decimal_face_setup, \
minute_repeater_decimal_face_activate, \
minute_repeater_decimal_face_loop, \
minute_repeater_decimal_face_resign, \
minute_repeater_decimal_face_advise, \
})
#endif // MINUTE_REPEATER_DECIMAL_FACE_H_

View File

@@ -1,221 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Jonas Termeau
*
* 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 "repetition_minute_face.h"
#include "watch.h"
#include "watch_utility.h"
#include "watch_private_display.h"
void play_hour_chime(void) {
watch_buzzer_play_note(BUZZER_NOTE_C6, 75);
watch_buzzer_play_note(BUZZER_NOTE_REST, 500);
}
void play_quarter_chime(void) {
watch_buzzer_play_note(BUZZER_NOTE_E6, 75);
watch_buzzer_play_note(BUZZER_NOTE_REST, 150);
watch_buzzer_play_note(BUZZER_NOTE_C6, 75);
watch_buzzer_play_note(BUZZER_NOTE_REST, 750);
}
void play_minute_chime(void) {
watch_buzzer_play_note(BUZZER_NOTE_E6, 75);
watch_buzzer_play_note(BUZZER_NOTE_REST, 500);
}
static void _update_alarm_indicator(bool settings_alarm_enabled, repetition_minute_state_t *state) {
state->alarm_enabled = settings_alarm_enabled;
if (state->alarm_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL);
else watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
}
void repetition_minute_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(repetition_minute_state_t));
repetition_minute_state_t *state = (repetition_minute_state_t *)*context_ptr;
state->signal_enabled = false;
state->watch_face_index = watch_face_index;
}
}
void repetition_minute_face_activate(void *context) {
repetition_minute_state_t *state = (repetition_minute_state_t *)context;
if (watch_sleep_animation_is_running()) watch_stop_sleep_animation();
if (movement_clock_mode_24h()) watch_set_indicator(WATCH_INDICATOR_24H);
// handle chime indicator
if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL);
else watch_clear_indicator(WATCH_INDICATOR_BELL);
// show alarm indicator if there is an active alarm
_update_alarm_indicator(movement_alarm_enabled(), state);
watch_set_colon();
// this ensures that none of the timestamp fields will match, so we can re-render them all.
state->previous_date_time = 0xFFFFFFFF;
}
bool repetition_minute_face_loop(movement_event_t event, void *context) {
repetition_minute_state_t *state = (repetition_minute_state_t *)context;
char buf[11];
uint8_t pos;
watch_date_time_t date_time;
uint32_t previous_date_time;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
date_time = watch_rtc_get_date_time();
previous_date_time = state->previous_date_time;
state->previous_date_time = date_time.reg;
// check the battery voltage once a day...
if (date_time.unit.day != state->last_battery_check) {
state->last_battery_check = date_time.unit.day;
watch_enable_adc();
uint16_t voltage = watch_get_vcc_voltage();
watch_disable_adc();
// 2.2 volts will happen when the battery has maybe 5-10% remaining?
// we can refine this later.
state->battery_low = (voltage < 2200);
}
// ...and set the LAP indicator if low.
if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP);
if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
// everything before seconds is the same, don't waste cycles setting those segments.
watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8);
watch_display_character_lp_seconds('0' + date_time.unit.second % 10, 9);
break;
} else if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
// everything before minutes is the same.
pos = 6;
sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second);
} else {
// other stuff changed; let's do it all.
if (!movement_clock_mode_24h()) {
// if we are in 12 hour mode, do some cleanup.
if (date_time.unit.hour < 12) {
watch_clear_indicator(WATCH_INDICATOR_PM);
} else {
watch_set_indicator(WATCH_INDICATOR_PM);
}
date_time.unit.hour %= 12;
if (date_time.unit.hour == 0) date_time.unit.hour = 12;
}
pos = 0;
if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
if (!watch_sleep_animation_is_running()) watch_start_sleep_animation(500);
sprintf(buf, "%s%2d%2d%02d ", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute);
} else {
sprintf(buf, "%s%2d%2d%02d%02d", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second);
}
}
watch_display_string(buf, pos);
// handle alarm indicator
if (state->alarm_enabled != movement_alarm_enabled()) _update_alarm_indicator(movement_alarm_enabled(), state);
break;
case EVENT_ALARM_LONG_PRESS:
state->signal_enabled = !state->signal_enabled;
if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL);
else watch_clear_indicator(WATCH_INDICATOR_BELL);
break;
case EVENT_BACKGROUND_TASK:
// uncomment this line to snap back to the clock face when the hour signal sounds:
// movement_move_to_face(state->watch_face_index);
movement_play_signal();
break;
case EVENT_LIGHT_LONG_UP:
/*
* Howdy neighbors, this is the actual complication. Like an actual
* (very expensive) watch with a repetition minute complication it's
* boring at 00:00 or 1:00 and very quite musical at 23:59 or 12:59.
*/
date_time = watch_rtc_get_date_time();
int hours = date_time.unit.hour;
int quarters = date_time.unit.minute / 15;
int minutes = date_time.unit.minute % 15;
// chiming hours
if (!movement_clock_mode_24h()) {
hours = date_time.unit.hour % 12;
if (hours == 0) hours = 12;
}
if (hours > 0) {
int count = 0;
for(count = hours; count > 0; --count) {
play_hour_chime();
}
}
// chiming quarters (if needed)
if (quarters > 0) {
int count = 0;
for(count = quarters; count > 0; --count) {
play_quarter_chime();
}
}
// chiming minutes (if needed)
if (minutes > 0) {
int count = 0;
for(count = minutes; count > 0; --count) {
play_minute_chime();
}
}
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void repetition_minute_face_resign(void *context) {
(void) context;
}
movement_watch_face_advisory_t repetition_minute_face_advise(void *context) {
repetition_minute_state_t *state = (repetition_minute_state_t *)context;
movement_watch_face_advisory_t retval = { 0 };
if (state->signal_enabled) {
watch_date_time_t date_time = watch_rtc_get_date_time();
retval.wants_background_task = date_time.unit.minute == 0;
}
return retval;
}

View File

@@ -1,83 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Jonas Termeau
*
* 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 REPETITION_MINUTE_FACE_H_
#define REPETITION_MINUTE_FACE_H_
/*
* REPETITION MINUTE face
*
* A hopefully useful complication for friendly neighbors in the dark
*
* Originating from 1676 from reverend and mechanician Edward Barlow, and
* perfected in 1820 by neighbor Abraham Breguet, a minute repeater or
* "repetition minute" is a complication in a mechanical watch or clock that
* chimes the hours and often minutes at the press of a button. There are many
* types of repeater, from the simple repeater which merely strikes the number
* of hours, to the minute repeater which chimes the time down to the minute,
* using separate tones for hours, quarter hours, and minutes. They originated
* before widespread artificial illumination, to allow the time to be determined
* in the dark, and were also used by the visually impaired.
*
* How to use it :
*
* Long press the light button to get an auditive reading of the time like so :
* 0..23 (1..12 if 24-hours format isn't enabled) low beep(s) for the hours
* 0..3 low-high couple pitched beeps for the quarters
* 0..14 high pitched beep(s) for the remaining minutes
*
* Prerequisite : a watch with a working buzzer
*
* ~ Only in the darkness can you see the stars. - Martin Luther King ~
*/
#include "movement.h"
typedef struct {
uint32_t previous_date_time;
uint8_t last_battery_check;
uint8_t watch_face_index;
bool signal_enabled;
bool battery_low;
bool alarm_enabled;
} repetition_minute_state_t;
void play_hour_chime(void);
void play_quarter_chime(void);
void play_minute_chime(void);
void repetition_minute_face_setup(uint8_t watch_face_index, void ** context_ptr);
void repetition_minute_face_activate(void *context);
bool repetition_minute_face_loop(movement_event_t event, void *context);
void repetition_minute_face_resign(void *context);
movement_watch_face_advisory_t repetition_minute_face_advise(void *context);
#define repetition_minute_face ((const watch_face_t){ \
repetition_minute_face_setup, \
repetition_minute_face_activate, \
repetition_minute_face_loop, \
repetition_minute_face_resign, \
repetition_minute_face_advise, \
})
#endif // REPETITION_MINUTE_FACE_H_

View File

@@ -1,222 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Andreas Nebinger, based on Joey Castillo's simple clock face
*
* 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 "simple_clock_bin_led_face.h"
#include "watch.h"
#include "watch_utility.h"
#include "watch_private_display.h"
static void _update_alarm_indicator(bool settings_alarm_enabled, simple_clock_bin_led_state_t *state) {
state->alarm_enabled = settings_alarm_enabled;
if (state->alarm_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL);
else watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
}
static void _display_left_aligned(uint8_t value) {
if (value >= 10) {
watch_display_character('0' + value / 10, 4);
watch_display_character('0' + value % 10, 5);
} else {
watch_display_character('0' + value, 4);
watch_display_character(' ', 5);
}
}
void simple_clock_bin_led_face_setup(uint8_t watch_face_index, void ** context_ptr) {
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(simple_clock_bin_led_state_t));
memset(*context_ptr, 0, sizeof(simple_clock_bin_led_state_t));
simple_clock_bin_led_state_t *state = (simple_clock_bin_led_state_t *)*context_ptr;
state->watch_face_index = watch_face_index;
}
}
void simple_clock_bin_led_face_activate(void *context) {
simple_clock_bin_led_state_t *state = (simple_clock_bin_led_state_t *)context;
if (watch_sleep_animation_is_running()) watch_stop_sleep_animation();
if (movement_clock_mode_24h()) watch_set_indicator(WATCH_INDICATOR_24H);
// handle chime indicator
if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL);
else watch_clear_indicator(WATCH_INDICATOR_BELL);
// show alarm indicator if there is an active alarm
_update_alarm_indicator(movement_alarm_enabled(), state);
watch_set_colon();
// this ensures that none of the timestamp fields will match, so we can re-render them all.
state->previous_date_time = 0xFFFFFFFF;
}
bool simple_clock_bin_led_face_loop(movement_event_t event, void *context) {
simple_clock_bin_led_state_t *state = (simple_clock_bin_led_state_t *)context;
char buf[11];
uint8_t pos;
watch_date_time_t date_time;
uint32_t previous_date_time;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
date_time = watch_rtc_get_date_time();
if (state->flashing_state > 0) {
if (state->ticks) {
state->ticks--;
} else {
if (state->flashing_state & 64) {
// start led on for current bit
state->flashing_state &= 63;
state->ticks = (state->flashing_value & 1 ? 7 : 1);
movement_illuminate_led();
} else {
// indicate first or switch to next bit
watch_set_led_off();
if ((state->flashing_state & 128) == 0) state->flashing_value = state->flashing_value >> 1;
if (state->flashing_value || (state->flashing_state & 128)) {
state->flashing_state &= 127;
state->flashing_state |= 64;
state->ticks = 6;
} else if (state->flashing_state & 1) {
// transition to minutes
state->flashing_state = 2 + 128;
state->flashing_value = date_time.unit.minute;
_display_left_aligned(state->flashing_value);
state->ticks = 9;
} else {
// end flashing
state->flashing_state = 0;
state->previous_date_time = 0xFFFFFFFF;
movement_request_tick_frequency(1);
watch_set_colon();
}
}
}
} else {
previous_date_time = state->previous_date_time;
state->previous_date_time = date_time.reg;
// check the battery voltage once a day...
if (date_time.unit.day != state->last_battery_check) {
state->last_battery_check = date_time.unit.day;
watch_enable_adc();
uint16_t voltage = watch_get_vcc_voltage();
watch_disable_adc();
// 2.2 volts will happen when the battery has maybe 5-10% remaining?
// we can refine this later.
state->battery_low = (voltage < 2200);
}
// ...and set the LAP indicator if low.
if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP);
if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
// everything before seconds is the same, don't waste cycles setting those segments.
watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8);
watch_display_character_lp_seconds('0' + date_time.unit.second % 10, 9);
break;
} else if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
// everything before minutes is the same.
pos = 6;
sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second);
} else {
// other stuff changed; let's do it all.
if (!movement_clock_mode_24h()) {
// if we are in 12 hour mode, do some cleanup.
if (date_time.unit.hour < 12) {
watch_clear_indicator(WATCH_INDICATOR_PM);
} else {
watch_set_indicator(WATCH_INDICATOR_PM);
}
date_time.unit.hour %= 12;
if (date_time.unit.hour == 0) date_time.unit.hour = 12;
}
pos = 0;
if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
if (!watch_sleep_animation_is_running()) watch_start_sleep_animation(500);
sprintf(buf, "%s%2d%2d%02d ", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute);
} else {
sprintf(buf, "%s%2d%2d%02d%02d", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second);
}
}
watch_display_string(buf, pos);
// handle alarm indicator
if (state->alarm_enabled != movement_alarm_enabled()) _update_alarm_indicator(movement_alarm_enabled(), state);
}
break;
case EVENT_ALARM_LONG_PRESS:
state->signal_enabled = !state->signal_enabled;
if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL);
else watch_clear_indicator(WATCH_INDICATOR_BELL);
break;
case EVENT_BACKGROUND_TASK:
// uncomment this line to snap back to the clock face when the hour signal sounds:
// movement_move_to_face(state->watch_face_index);
movement_play_signal();
break;
case EVENT_LIGHT_LONG_PRESS:
if (state->flashing_state == 0) {
date_time = watch_rtc_get_date_time();
state->flashing_state = 1 + 128;
state->ticks = 4;
if (!movement_clock_mode_24h()) {
date_time.unit.hour %= 12;
if (date_time.unit.hour == 0) date_time.unit.hour = 12;
}
watch_display_string(" ", 4);
_display_left_aligned(date_time.unit.hour);
state->flashing_value = date_time.unit.hour > 12 ? date_time.unit.hour - 12 : date_time.unit.hour;
watch_set_led_off();
watch_clear_colon();
movement_request_tick_frequency(8);
}
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void simple_clock_bin_led_face_resign(void *context) {
(void) context;
}
movement_watch_face_advisory_t simple_clock_bin_led_face_advise(void *context) {
simple_clock_bin_led_state_t *state = (simple_clock_bin_led_state_t *)context;
movement_watch_face_advisory_t retval = { 0 };
if (state->signal_enabled) {
watch_date_time_t date_time = watch_rtc_get_date_time();
retval.wants_background_task = date_time.unit.minute == 0;
}
return retval;
}

View File

@@ -1,82 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Andreas Nebinger, based on Joey Castillo's simple clock face
*
* 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 SIIMPLE_CLOCK_BIN_LED_FACE_H_
#define SIIMPLE_CLOCK_BIN_LED_FACE_H_
/*
* BINARY LED CLOCK FACE
*
* A "fork" of the simple clock face, which provides the functionality of showing
* the current time by flashing the LED using binary representation.
*
* This feature serves as a practical solution to compensate for the admittedly
* subpar backlight of the F91w watch and is especially useful if your eyesight
* is not the best. By pressing and holding the light button long enough, the
* watch will illuminate the LED to showcase the current time.
*
* How to interpret the flashing led:
* - Firstly, the hour is presented as a binary number, with the lowest bit displayed
* first. A short flash signifies 0, while a longer flash represents 1. If you use
* the watch in 24h mode, please note that the indicated value may be decreased
* by 12, to keep things simple and short. For example, 22h would be translated
* to 10h.
* - After showing the hour, a lengthier pause indicates that minutes will be shown
* next.
* - Similar to the hour representation, minutes are displayed in binary format,
* starting with the lowest bits. A short flash denotes 0, a longer flash
* represents 1.
*/
#include "movement.h"
typedef struct {
uint32_t previous_date_time;
uint8_t last_battery_check;
uint8_t watch_face_index;
bool signal_enabled;
bool battery_low;
bool alarm_enabled;
uint8_t flashing_state; // bitmap representing the flashing state. Bit 0 = hours showing, bit 1 = minutes showing,
// bit 6 = short break between flashing bits, bit 7 = long break between hours and minutes
uint8_t flashing_value;
uint8_t ticks;
} simple_clock_bin_led_state_t;
void simple_clock_bin_led_face_setup(uint8_t watch_face_index, void ** context_ptr);
void simple_clock_bin_led_face_activate(void *context);
bool simple_clock_bin_led_face_loop(movement_event_t event, void *context);
void simple_clock_bin_led_face_resign(void *context);
movement_watch_face_advisory_t simple_clock_bin_led_face_advise(void *context);
#define simple_clock_bin_led_face ((const watch_face_t){ \
simple_clock_bin_led_face_setup, \
simple_clock_bin_led_face_activate, \
simple_clock_bin_led_face_loop, \
simple_clock_bin_led_face_resign, \
simple_clock_bin_led_face_advise, \
})
#endif // SIIMPLE_CLOCK_BIN_LED_FACE_H_

View File

@@ -1,156 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2022 Joey Castillo
*
* 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 "weeknumber_clock_face.h"
#include "watch.h"
#include "watch_utility.h"
static void _update_alarm_indicator(bool settings_alarm_enabled, weeknumber_clock_state_t *state) {
state->alarm_enabled = settings_alarm_enabled;
if (state->alarm_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL);
else watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
}
void weeknumber_clock_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(weeknumber_clock_state_t));
weeknumber_clock_state_t *state = (weeknumber_clock_state_t *)*context_ptr;
state->signal_enabled = false;
state->watch_face_index = watch_face_index;
}
}
void weeknumber_clock_face_activate(void *context) {
weeknumber_clock_state_t *state = (weeknumber_clock_state_t *)context;
if (watch_sleep_animation_is_running()) watch_stop_sleep_animation();
if (movement_clock_mode_24h()) watch_set_indicator(WATCH_INDICATOR_24H);
// handle chime indicator
if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL);
else watch_clear_indicator(WATCH_INDICATOR_BELL);
// show alarm indicator if there is an active alarm
_update_alarm_indicator(movement_alarm_enabled(), state);
watch_set_colon();
// this ensures that none of the timestamp fields will match, so we can re-render them all.
state->previous_date_time = 0xFFFFFFFF;
}
bool weeknumber_clock_face_loop(movement_event_t event, void *context) {
weeknumber_clock_state_t *state = (weeknumber_clock_state_t *)context;
char buf[11];
uint8_t pos;
watch_date_time_t date_time;
uint32_t previous_date_time;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
date_time = watch_rtc_get_date_time();
previous_date_time = state->previous_date_time;
state->previous_date_time = date_time.reg;
// check the battery voltage once a day...
if (date_time.unit.day != state->last_battery_check) {
state->last_battery_check = date_time.unit.day;
watch_enable_adc();
uint16_t voltage = watch_get_vcc_voltage();
watch_disable_adc();
// 2.2 volts will happen when the battery has maybe 5-10% remaining?
// we can refine this later.
state->battery_low = (voltage < 2200);
}
// ...and set the LAP indicator if low.
if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP);
if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
// everything before minutes is the same.
pos = 6;
sprintf(buf, "%02d%02d", date_time.unit.minute, watch_utility_get_weeknumber(date_time.unit.year, date_time.unit.month, date_time.unit.day));
} else {
// other stuff changed; let's do it all.
if (!movement_clock_mode_24h()) {
// if we are in 12 hour mode, do some cleanup.
if (date_time.unit.hour < 12) {
watch_clear_indicator(WATCH_INDICATOR_PM);
} else {
watch_set_indicator(WATCH_INDICATOR_PM);
}
date_time.unit.hour %= 12;
if (date_time.unit.hour == 0) date_time.unit.hour = 12;
}
pos = 0;
if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
if (!watch_sleep_animation_is_running()) watch_start_sleep_animation(500);
sprintf(buf, "%s%2d%2d%02d ", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute);
} else {
sprintf(buf, "%s%2d%2d%02d%02d", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute, watch_utility_get_weeknumber(date_time.unit.year, date_time.unit.month, date_time.unit.day));
}
}
watch_display_string(buf, pos);
// handle alarm indicator
if (state->alarm_enabled != movement_alarm_enabled()) _update_alarm_indicator(movement_alarm_enabled(), state);
break;
case EVENT_ALARM_LONG_PRESS:
state->signal_enabled = !state->signal_enabled;
if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL);
else watch_clear_indicator(WATCH_INDICATOR_BELL);
break;
case EVENT_BACKGROUND_TASK:
// uncomment this line to snap back to the clock face when the hour signal sounds:
// movement_move_to_face(state->watch_face_index);
movement_play_signal();
break;
default:
movement_default_loop_handler(event);
break;
}
return true;
}
void weeknumber_clock_face_resign(void *context) {
(void) context;
}
movement_watch_face_advisory_t weeknumber_clock_face_advise(void *context) {
weeknumber_clock_state_t *state = (weeknumber_clock_state_t *)context;
movement_watch_face_advisory_t retval = { 0 };
if (state->signal_enabled) {
watch_date_time_t date_time = watch_rtc_get_date_time();
retval.wants_background_task = date_time.unit.minute == 0;
}
return retval;
}

View File

@@ -1,61 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2022 Joey Castillo
*
* 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 WEEKNUMBER_CLOCK_FACE_H_
#define WEEKNUMBER_CLOCK_FACE_H_
/*
* WEEK-NUMBER WATCH FACE
*
* Same as simple clock, but has iso 8601 week number instead of seconds counter.
*
* Long-press ALARM to toggle the hourly chime.
*/
#include "movement.h"
typedef struct {
uint32_t previous_date_time;
uint8_t last_battery_check;
uint8_t watch_face_index;
bool signal_enabled;
bool battery_low;
bool alarm_enabled;
} weeknumber_clock_state_t;
void weeknumber_clock_face_setup(uint8_t watch_face_index, void ** context_ptr);
void weeknumber_clock_face_activate(void *context);
bool weeknumber_clock_face_loop(movement_event_t event, void *context);
void weeknumber_clock_face_resign(void *context);
movement_watch_face_advisory_t weeknumber_clock_face_advise(void *context);
#define weeknumber_clock_face ((const watch_face_t){ \
weeknumber_clock_face_setup, \
weeknumber_clock_face_activate, \
weeknumber_clock_face_loop, \
weeknumber_clock_face_resign, \
weeknumber_clock_face_advise, \
})
#endif // SIMPLE_CLOCK_FACE_H_

View File

@@ -1,374 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Konrad Rieck
* Copyright (c) 2022 Joey Castillo
*
* 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 "world_clock2_face.h"
#include "watch.h"
#include "watch_utility.h"
#include "watch_utility.h"
static bool refresh_face;
/* Simple macros for navigation */
#define FORWARD +1
#define BACKWARD -1
/* Activate refresh of time */
#define REFRESH_TIME 0xffffffff
/* List of all time zone names */
const char *zone_names[] = {
"UTC", // 0 : 0:00:00 (UTC)
"CET", // 1 : 1:00:00 (Central European Time)
"SAST", // 2 : 2:00:00 (South African Standard Time)
"ARST", // 3 : 3:00:00 (Arabia Standard Time)
"IRST", // 4 : 3:30:00 (Iran Standard Time)
"GET", // 5 : 4:00:00 (Georgia Standard Time)
"AFT", // 6 : 4:30:00 (Afghanistan Time)
"PKT", // 7 : 5:00:00 (Pakistan Standard Time)
"IST", // 8 : 5:30:00 (Indian Standard Time)
"NPT", // 9 : 5:45:00 (Nepal Time)
"KGT", // 10 : 6:00:00 (Kyrgyzstan time)
"MYST", // 11 : 6:30:00 (Myanmar Time)
"THA", // 12 : 7:00:00 (Thailand Standard Time)
"CST", // 13 : 8:00:00 (China Standard Time, Australian Western Standard Time)
"ACWS", // 14 : 8:45:00 (Australian Central Western Standard Time)
"JST", // 15 : 9:00:00 (Japan Standard Time, Korea Standard Time)
"ACST", // 16 : 9:30:00 (Australian Central Standard Time)
"AEST", // 17 : 10:00:00 (Australian Eastern Standard Time)
"LHST", // 18 : 10:30:00 (Lord Howe Standard Time)
"SBT", // 19 : 11:00:00 (Solomon Islands Time)
"NZST", // 20 : 12:00:00 (New Zealand Standard Time)
"CHAS", // 21 : 12:45:00 (Chatham Standard Time)
"TOT", // 22 : 13:00:00 (Tonga Time)
"CHAD", // 23 : 13:45:00 (Chatham Daylight Time)
"LINT", // 24 : 14:00:00 (Line Islands Time)
"BIT", // 25 : -12:00:00 (Baker Island Time)
"NUT", // 26 : -11:00:00 (Niue Time)
"HST", // 27 : -10:00:00 (Hawaii-Aleutian Standard Time)
"MART", // 28 : -9:30:00 (Marquesas Islands Time)
"AKST", // 29 : -9:00:00 (Alaska Standard Time)
"PST", // 30 : -8:00:00 (Pacific Standard Time)
"MST", // 31 : -7:00:00 (Mountain Standard Time)
"CST", // 32 : -6:00:00 (Central Standard Time)
"EST", // 33 : -5:00:00 (Eastern Standard Time)
"VET", // 34 : -4:30:00 (Venezuelan Standard Time)
"AST", // 35 : -4:00:00 (Atlantic Standard Time)
"NST", // 36 : -3:30:00 (Newfoundland Standard Time)
"BRT", // 37 : -3:00:00 (Brasilia Time)
"NDT", // 38 : -2:30:00 (Newfoundland Daylight Time)
"FNT", // 39 : -2:00:00 (Fernando de Noronha Time)
"AZOT", // 40 : -1:00:00 (Azores Standard Time)
};
/* Modulo function */
static inline unsigned int mod(int a, int b)
{
int r = a % b;
return r < 0 ? r + b : r;
}
/* Find the next selected time zone */
static inline uint8_t find_selected_zone(world_clock2_state_t *state, int direction)
{
uint8_t i = state->current_zone;
do {
i = mod(i + direction, NUM_TIME_ZONES);
/* Could not find a selected zone. Return UTC */
if (i == state->current_zone) {
return 0;
}
} while (!state->zones[i].selected);
return i;
}
/* Beep when zone is enabled. An octave up */
static void beep_enable() {
watch_buzzer_play_note(BUZZER_NOTE_G7, 50);
watch_buzzer_play_note(BUZZER_NOTE_REST, 75);
watch_buzzer_play_note(BUZZER_NOTE_C8, 75);
}
/* Beep when zone id disable. An octave down */
static void beep_disable() {
watch_buzzer_play_note(BUZZER_NOTE_C8, 50);
watch_buzzer_play_note(BUZZER_NOTE_REST, 75);
watch_buzzer_play_note(BUZZER_NOTE_G7, 75);
}
void world_clock2_face_setup(uint8_t watch_face_index, void **context_ptr)
{
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(world_clock2_state_t));
memset(*context_ptr, 0, sizeof(world_clock2_state_t));
/* Start in settings mode */
world_clock2_state_t *state = (world_clock2_state_t *) * context_ptr;
state->current_mode = WORLD_CLOCK2_MODE_SETTINGS;
}
}
void world_clock2_face_activate(void *context)
{
world_clock2_state_t *state = (world_clock2_state_t *) context;
if (watch_sleep_animation_is_running())
watch_stop_sleep_animation();
switch (state->current_mode) {
case WORLD_CLOCK2_MODE_DISPLAY:
/* Normal tick frequency */
movement_request_tick_frequency(1);
break;
case WORLD_CLOCK2_MODE_SETTINGS:
/* Faster frequency for blinking effect */
movement_request_tick_frequency(4);
break;
}
refresh_face = true;
}
static bool mode_display(movement_event_t event, world_clock2_state_t *state)
{
char buf[11];
uint8_t pos;
uint32_t timestamp;
uint32_t previous_date_time;
watch_date_time_t date_time;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
/* Update indicators and colon on refresh */
if (refresh_face) {
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
watch_set_colon();
if (movement_clock_mode_24h())
watch_set_indicator(WATCH_INDICATOR_24H);
state->previous_date_time = REFRESH_TIME;
refresh_face = false;
}
/* Determine current time at time zone and store date/time */
date_time = watch_rtc_get_date_time();
timestamp = watch_utility_date_time_to_unix_time(date_time, movement_get_current_timezone_offset());
date_time = watch_utility_date_time_from_unix_time(timestamp, movement_timezone_offsets[state->current_zone] * 60);
previous_date_time = state->previous_date_time;
state->previous_date_time = date_time.reg;
if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
/* Everything before seconds is the same, don't waste cycles setting those segments. */
pos = 8;
sprintf(buf, "%02d", date_time.unit.second);
} else if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
/* Everything before minutes is the same. */
pos = 6;
sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second);
} else {
/* Other stuff changed; Let's do it all. */
if (!movement_clock_mode_24h()) {
/* If we are in 12 hour mode, do some cleanup. */
if (date_time.unit.hour < 12) {
watch_clear_indicator(WATCH_INDICATOR_PM);
} else {
watch_set_indicator(WATCH_INDICATOR_PM);
}
date_time.unit.hour %= 12;
if (date_time.unit.hour == 0)
date_time.unit.hour = 12;
}
pos = 0;
if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
if (!watch_sleep_animation_is_running())
watch_start_sleep_animation(500);
sprintf(buf, "%.2s%2d%2d%02d ",
zone_names[state->current_zone],
date_time.unit.day,
date_time.unit.hour,
date_time.unit.minute);
} else {
sprintf(buf, "%.2s%2d%2d%02d%02d",
zone_names[state->current_zone],
date_time.unit.day,
date_time.unit.hour,
date_time.unit.minute,
date_time.unit.second);
}
}
watch_display_string(buf, pos);
break;
case EVENT_ALARM_BUTTON_UP:
state->current_zone = find_selected_zone(state, FORWARD);
state->previous_date_time = REFRESH_TIME;
break;
case EVENT_LIGHT_BUTTON_DOWN:
/* Do nothing. */
break;
case EVENT_LIGHT_BUTTON_UP:
state->current_zone = find_selected_zone(state, BACKWARD);
state->previous_date_time = REFRESH_TIME;
break;
case EVENT_LIGHT_LONG_PRESS:
movement_illuminate_led();
break;
case EVENT_ALARM_LONG_PRESS:
/* Switch to settings mode */
state->current_mode = WORLD_CLOCK2_MODE_SETTINGS;
refresh_face = true;
movement_request_tick_frequency(1);
if (movement_button_should_sound())
watch_buzzer_play_note(BUZZER_NOTE_C8, 50);
break;
case EVENT_MODE_BUTTON_UP:
/* Reset frequency and move to next face */
movement_request_tick_frequency(1);
movement_move_to_next_face();
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
static bool mode_settings(movement_event_t event, world_clock2_state_t *state)
{
char buf[11];
int8_t hours, minutes;
uint8_t zone;
div_t result;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
/* Update indicator and colon on refresh */
if (refresh_face) {
watch_clear_colon();
watch_clear_indicator(WATCH_INDICATOR_24H);
watch_clear_indicator(WATCH_INDICATOR_PM);
refresh_face = false;
}
result = div(movement_timezone_offsets[state->current_zone], 60);
hours = result.quot;
minutes = result.rem;
/*
* Display time zone. The range of the parameters is reduced
* to avoid accidentally overflowing the buffer and to suppress
* corresponding compiler warnings.
*/
sprintf(buf, "%.2s%2d %c%02d%02d",
zone_names[state->current_zone],
state->current_zone % 100,
hours < 0 ? '-' : '+',
abs(hours) % 24,
abs(minutes) % 60);
/* Let the zone number blink */
if (event.subsecond % 2)
buf[2] = buf[3] = ' ';
if (state->zones[state->current_zone].selected)
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
else
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
watch_display_string(buf, 0);
break;
case EVENT_ALARM_BUTTON_UP:
state->current_zone = mod(state->current_zone + FORWARD, NUM_TIME_ZONES);
break;
case EVENT_LIGHT_BUTTON_UP:
state->current_zone = mod(state->current_zone + BACKWARD, NUM_TIME_ZONES);
break;
case EVENT_LIGHT_BUTTON_DOWN:
/* Do nothing */
break;
case EVENT_ALARM_LONG_PRESS:
/* Find next selected zone */
if (!state->zones[state->current_zone].selected)
state->current_zone = find_selected_zone(state, FORWARD);
/* Switch to display mode */
state->current_mode = WORLD_CLOCK2_MODE_DISPLAY;
refresh_face = true;
movement_request_tick_frequency(1);
if (movement_button_should_sound())
watch_buzzer_play_note(BUZZER_NOTE_C8, 50);
break;
case EVENT_LIGHT_LONG_PRESS:
/* Toggle selection of current zone */
zone = state->current_zone;
state->zones[zone].selected = !state->zones[zone].selected;
if (movement_button_should_sound()) {
if (state->zones[zone].selected) {
beep_enable();
} else {
beep_disable();
}
}
break;
case EVENT_MODE_BUTTON_UP:
/* Reset frequency and move to next face */
movement_request_tick_frequency(1);
movement_move_to_next_face();
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
bool world_clock2_face_loop(movement_event_t event, void *context)
{
world_clock2_state_t *state = (world_clock2_state_t *) context;
switch (state->current_mode) {
case WORLD_CLOCK2_MODE_DISPLAY:
return mode_display(event, state);
case WORLD_CLOCK2_MODE_SETTINGS:
return mode_settings(event, state);
}
return false;
}
void world_clock2_face_resign(void *context)
{
(void) context;
}

View File

@@ -1,122 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 Konrad Rieck
* Copyright (c) 2022 Joey Castillo
*
* 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 WORLD_CLOCK2_FACE_H_
#define WORLD_CLOCK2_FACE_H_
/*
* WORLD CLOCK 2
*
* This is an alternative world clock face that allows the user to cycle
* through a list of selected time zones. It extends the original
* implementation by Joey Castillo. The face has two modes: display mode
* and settings mode.
*
* Settings mode
*
* When the clock face is activated for the first time, it enters settings
* mode. Here, the user can select the time zones they want to display. The
* face shows a summary of the current time zone:
* * The top of the face displays the first two letters of the time zone
* abbreviation, such as "PS" for Pacific Standard Time or CE for
* "Central European Time".
* * The upper-right corner shows the index number of the time zone. This
* helps avoid confusion when multiple time zones have the same two-letter
* abbreviation.
* * The main display shows the offset from UTC, with a "+" indicating a
* positive offset and a "-" indicating a negative offset. For example,
* the offset for Japanese Standard Time is displayed as "+9:00".
*
* The user can navigate through the time zones and select them using the
* following buttons:
* * The ALARM button moves forward to the next time zone, while the LIGHT
* button moves backward to the previous zone. This way, the user can
* cycle through all 41 supported time zones.
* * A long press on the LIGHT button selects the current time zone, and
* the signal indicator appears at the top left. Another long press of
* the LIGHT button deselects the time zone.
* * A long press on the ALARM button exits settings mode and returns to
* display mode.
*
* Display mode
*
* In the display mode, the face shows the time of the currently selected
* time zone. The face includes the following components:
* * The top of the face displays the first two letters of the time zone
* abbreviation, such as "PS" for Pacific Standard Time or "CE" for
* Central European Time.
* * The upper-right corner shows the current day of the month, which helps
* indicate time zones that cross the international date line with respect
* to the local time.
* * The main display shows the time in the selected time zone in either
* 12-hour or 24-hour form. There is no timeout, allowing users to keep
* the chosen time zone displayed for as long as they wish.
*
* The user can navigate through the selected time zones using the following
* buttons:
* * The ALARM button moves to the next selected time zone, while the LIGHT
* button moves to the previous zone. If no time zone is selected, the
* face simply shows UTC.
* * A long press on the ALARM button enters settings mode and enables the
* user to re-configure the selected time zones.
* * A long press on the LIGHT button activates the LED illumination of the
* watch.
*/
/* Number of zones. See movement_timezone_offsets. */
#define NUM_TIME_ZONES 41
#include "movement.h"
typedef enum {
WORLD_CLOCK2_MODE_DISPLAY,
WORLD_CLOCK2_MODE_SETTINGS
} world_clock2_mode_t;
typedef struct {
bool selected;
} world_clock2_zone_t;
typedef struct {
world_clock2_zone_t zones[NUM_TIME_ZONES];
world_clock2_mode_t current_mode;
uint8_t current_zone;
uint32_t previous_date_time;
} world_clock2_state_t;
void world_clock2_face_setup(uint8_t watch_face_index, void **context_ptr);
void world_clock2_face_activate(void *context);
bool world_clock2_face_loop(movement_event_t event, void *context);
void world_clock2_face_resign(void *context);
#define world_clock2_face ((const watch_face_t){ \
world_clock2_face_setup, \
world_clock2_face_activate, \
world_clock2_face_loop, \
world_clock2_face_resign, \
NULL, \
})
#endif /* WORLD_CLOCK2_FACE_H_ */

View File

@@ -1,207 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 <#author_name#>
*
* 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 "wyoscan_face.h"
#include "watch_private_display.h"
/*
Slowly render the current time from left to right,
scanning across its liquid crystal face, completing 1 cycle every 2 seconds.
Created to mimic the wyoscan watch that was produced by Halmos and designed by Dexter Sinister
It looks like this https://www.o-r-g.com/apps/wyoscan
Youll notice that reading this watch requires more attention than usual,
as the seven segments of each digit are lit one by one across its display.
This speed may be adjusted until it reaches the limits of your perception.
You and your watch are now in tune.
This is a relatively generic way of animating a time display.
If you want to modify the animation, you can change the segment_map
the A-F are corresponding to the segments on the watch face
A
F B
G
E C
D
the X's are the frames that will be skipped in the animation
This particular segment_map allocates 8 frames to display each number
this is to achieve the 2 second cycle time.
8 frames per number * 6 numbers + the trailing 16 frames = 64 frames
at 32 frames per second, this is a 2 second cycle time.
I tried to make the animation of each number display similar to if you were
to draw the number on the watch face with a pen, pausing with 'X'
when your pen might turn a corner or when you might cross over
a line you've already drawn. It is vaguely top to bottom and counter,
clockwise when possible.
*/
static char *segment_map[] = {
"AXFBDEXC", // 0
"BXXXCXXX", // 1
"ABGEXXXD", // 2
"ABGXXXCD", // 3
"FXGBXXXC", // 4
"AXFXGXCD", // 5
"AXFEDCXG", // 6
"AXXBXXCX", // 7
"AFGCDEXB", // 8
"AFGBXXCD" // 9
};
/*
This is the mapping of input to the watch_set_pixel() function
for each position in hhmmss it defines the 2 dimention input at each of A-F*/
static const int32_t clock_mapping[6][7][2] = {
// hour 1
{{1,18}, {2,19}, {0,19}, {1,18}, {0,18}, {2,18}, {1,19}},
// hour 2
{{2,20}, {2,21}, {1,21}, {0,21}, {0,20}, {1,17}, {1,20}},
// minute 1
{{0,22}, {2,23}, {0,23}, {0,22}, {1,22}, {2,22}, {1,23}},
// minute 2
{{2,1}, {2,10}, {0,1}, {0,0}, {1,0}, {2,0}, {1,1}},
// second 1
{{2,2}, {2,3}, {0,4}, {0,3}, {0,2}, {1,2}, {1,3}},
// second 2
{{2,4}, {2,5}, {1,6}, {0,6}, {0,5}, {1,4}, {1,5}},
};
void wyoscan_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(wyoscan_state_t));
memset(*context_ptr, 0, sizeof(wyoscan_state_t));
// Do any one-time tasks in here; the inside of this conditional happens only at boot.
}
// Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.
}
void wyoscan_face_activate(void *context) {
wyoscan_state_t *state = (wyoscan_state_t *)context;
movement_request_tick_frequency(32);
state->total_frames = 64;
}
bool wyoscan_face_loop(movement_event_t event, void *context) {
wyoscan_state_t *state = (wyoscan_state_t *)context;
watch_date_time_t date_time;
switch (event.event_type) {
case EVENT_ACTIVATE:
break;
case EVENT_TICK:
if (!state->animate) {
date_time = watch_rtc_get_date_time();
state->start = 0;
state->end = 0;
state->animation = 0;
state->animate = true;
state->time_digits[0] = date_time.unit.hour / 10;
state->time_digits[1] = date_time.unit.hour % 10;
state->time_digits[2] = date_time.unit.minute / 10;
state->time_digits[3] = date_time.unit.minute % 10;
state->time_digits[4] = date_time.unit.second / 10;
state->time_digits[5] = date_time.unit.second % 10;
}
if ( state->animate ) {
// if we have reached the max number of illuminated segments, we clear the oldest one
if ((state->end + 1) % MAX_ILLUMINATED_SEGMENTS == state->start) {
// clear the oldest pixel if it's not 'X'
if (state->illuminated_segments[state->start][0] != 99 && state->illuminated_segments[state->start][1] != 99) {
watch_clear_pixel(state->illuminated_segments[state->start][0], state->illuminated_segments[state->start][1]);
}
// increment the start index to point to the next oldest pixel
state->start = (state->start + 1) % MAX_ILLUMINATED_SEGMENTS;
}
if (state->animation < state->total_frames - MAX_ILLUMINATED_SEGMENTS) {
if (state->animation % 32 == 0) {
if (state->colon) {
watch_set_colon();
} else {
watch_clear_colon();
}
state->colon = !state->colon;
}
// calculate the start position for the current frame
state->position = (state->animation / 8) % 6;
// calculate the current segment for the current digit
state->segment = state->animation % strlen(segment_map[state->time_digits[state->position]]);
// get the segments for the current digit
state->segments = segment_map[state->time_digits[state->position]];
if (state->segments[state->segment] == 'X') {
// if 'X', skip this frame
state->illuminated_segments[state->end][0] = 99;
state->illuminated_segments[state->end][1] = 99;
state->end = (state->end + 1) % MAX_ILLUMINATED_SEGMENTS;
state->animation = (state->animation + 1);
break;
}
// calculate the animation frame
state->x = clock_mapping[state->position][state->segments[state->segment]-'A'][0];
state->y = clock_mapping[state->position][state->segments[state->segment]-'A'][1];
// set the new pixel
watch_set_pixel(state->x, state->y);
// store this pixel in the buffer
state->illuminated_segments[state->end][0] = state->x;
state->illuminated_segments[state->end][1] = state->y;
// increment the end index to the next position
state->end = (state->end + 1) % MAX_ILLUMINATED_SEGMENTS;
}
else if (state->animation >= state->total_frames - MAX_ILLUMINATED_SEGMENTS && state->animation < state->total_frames) {
state->end = (state->end + 1) % MAX_ILLUMINATED_SEGMENTS;
}
else {
// reset the animation state
state->animate = false;
}
state->animation = (state->animation + 1);
}
break;
case EVENT_LOW_ENERGY_UPDATE:
break;
case EVENT_ALARM_LONG_PRESS:
break;
case EVENT_BACKGROUND_TASK:
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void wyoscan_face_resign(void *context) {
(void) context;
// handle any cleanup before your watch face goes off-screen.
}

View File

@@ -1,91 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 <#author_name#>
*
* 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 WYOSCAN_FACE_H_
#define WYOSCAN_FACE_H_
/*
* WYOSCAN .5 hz watchface
*
* This is a recreation of the Wyoscan watch, which was a $175 watch in 2014.
* It was an f-91w pcb replacement.
*
* Video: https://user-images.githubusercontent.com/1795778/252550124-e07f0ed1-e328-4337-a654-fa1ee65d883f.mp4
* Background information: https://artmetropole.com/shop/11460
* Demo of what it looks like: https://www.o-r-g.com/apps/wyoscan
*
* 8 frames per number * 6 numbers + the trailing 16 frames = 64 frames
* at 32 frames per second, this is a 2-second cycle time or 0.5 Hz.
*
* It is giving me a stack overflow after about 2.5 cycles of the time display
* in the emulator, but it works fine on the watch.
*
* I'd like to make something for the low energy mode, but I haven't thought
* about how that might work, right now it just freezes in low energy mode
* until you press the 12-24HR button.
*
* There are no controls; it simply animates as long as the page is active.
*
*/
#include "movement.h"
#define MAX_ILLUMINATED_SEGMENTS 16
typedef struct {
uint32_t previous_date_time;
uint8_t last_battery_check;
uint8_t watch_face_index;
bool signal_enabled;
bool battery_low;
bool alarm_enabled;
uint8_t animation;
bool animate;
uint32_t start;
uint32_t end;
uint32_t total_frames;
bool colon;
uint8_t position, segment;
char *segments;
uint8_t x, y;
uint32_t time_digits[6];
uint32_t illuminated_segments[MAX_ILLUMINATED_SEGMENTS][2];
} wyoscan_state_t;
void wyoscan_face_setup(uint8_t watch_face_index, void ** context_ptr);
void wyoscan_face_activate(void *context);
bool wyoscan_face_loop(movement_event_t event, void *context);
void wyoscan_face_resign(void *context);
movement_watch_face_advisory_t wyoscan_face_advise(void *context);
#define wyoscan_face ((const watch_face_t){ \
wyoscan_face_setup, \
wyoscan_face_activate, \
wyoscan_face_loop, \
wyoscan_face_resign, \
NULL, \
})
#endif // WYOSCAN_FACE_H_