diff options
Diffstat (limited to 'movement/watch_faces/settings/finetune_face.c')
-rw-r--r-- | movement/watch_faces/settings/finetune_face.c | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/movement/watch_faces/settings/finetune_face.c b/movement/watch_faces/settings/finetune_face.c new file mode 100644 index 00000000..f328cbcb --- /dev/null +++ b/movement/watch_faces/settings/finetune_face.c @@ -0,0 +1,257 @@ +/* + * MIT License + * + * Copyright (c) 2022 Mikhail Svarichevsky https://3.14.by/ + * + * 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. + * + * FineTune face allows to align watch with sub-second precision in 25/250ms accuracy. + * Counts time since previous finetune, and allows to calculate & apply ppm correction for nanosec. + * + * Main screen - adjust delay (light/alarm) + * Long mode press - show hours since previous finetune + * Long mode press - show calculated ppm correction. You can apply it with long light, or just reset finetune timer with long alarm. + * + * Finetune will apply crystal aging correction on every finetune save (as aging is calculated since "last finetune" timestamp) - but you should worry + * about aging only on second/third years of watch calibration (if you are really looking at less than 10 seconds per year of error). + * + * Warning, do not use at the first second of a month, as you might stay at the same month and it will surprise you. + * Just wait 1 second...We are not fully replicating RTC timer behavior when RTC is off. + * Simulating months and years is... too much complexity. + * + */ + +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include "finetune_face.h" +#include "nanosec_face.h" +#include "watch_utility.h" + +extern nanosec_state_t nanosec_state; + +int total_adjustment; +int8_t finetune_page; + +void finetune_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + (void) context_ptr; + // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. +} + +void finetune_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + + // Handle any tasks related to your watch face coming on screen. + watch_display_string("FT", 0); + total_adjustment = 0; + finetune_page = 0; +} + +static float finetune_get_hours_passed(void) { + uint32_t current_time = watch_utility_date_time_to_unix_time(watch_rtc_get_date_time(), 0); + return (current_time - nanosec_state.last_correction_time) / 3600.0f; +} + +static float finetune_get_correction(void) { + return total_adjustment / (finetune_get_hours_passed() * 3600.0f) * 1000.0f; +} + +static void finetune_update_display(void) { + char buf[25]; + + if (finetune_page == 0) { + watch_display_string("FT", 0); + watch_date_time date_time = watch_rtc_get_date_time(); + sprintf(buf, "%02d", date_time.unit.second); + watch_display_string(buf, 8); + + sprintf(buf, "%04d", abs(total_adjustment)); + watch_display_string(buf, 4); + + if (total_adjustment < 0) { + watch_display_string("--", 2); + } else { + watch_display_string(" ", 2); + } + } else if (finetune_page == 1) { + float hours = finetune_get_hours_passed(); + + sprintf(buf, "DT %4d%02d", (int)hours, (int)(fmodf(hours, 1.) * 100)); + watch_display_string(buf, 0); + } else if (finetune_page == 2) { + if (finetune_get_hours_passed() < 6) { + sprintf(buf, " F 6HR "); + watch_display_string(buf, 0); + } else { + float correction = finetune_get_correction(); + sprintf(buf, " F%s%2d%04d", (total_adjustment < 0) ? " -" : " ", (int)fabsf(correction), (int)(remainderf(fabsf(correction), 1.) * 10000)); + watch_display_string(buf, 0); + } + } +} + +static void finetune_adjust_subseconds(int delta) { + // Update display first ot make it appear faster for the user + if (delta > 500) + total_adjustment += (delta - 1000); + else + total_adjustment += delta; + finetune_update_display(); + + // Then delay clock + watch_rtc_enable(false); + delay_ms(delta); + if (delta > 500) { + watch_date_time date_time = watch_rtc_get_date_time(); + date_time.unit.second = (date_time.unit.second + 1) % 60; + if (date_time.unit.second == 0) { // Overflow + date_time.unit.minute = (date_time.unit.minute + 1) % 60; + if (date_time.unit.minute == 0) { // Overflow + date_time.unit.hour = (date_time.unit.hour + 1) % 24; + if (date_time.unit.hour == 0) // Overflow + date_time.unit.day++; + } + } + watch_rtc_set_date_time(date_time); + } + watch_rtc_enable(true); +} + +static void finetune_update_correction_time(void) { + // Update aging, as we update correciton time - we must bake accrued aging into static offset + nanosec_state.freq_correction += roundf(nanosec_get_aging() * 100); + + // Remember when we last corrected time + nanosec_state.last_correction_time = watch_utility_date_time_to_unix_time(watch_rtc_get_date_time(), 0); + nanosec_save(); + movement_move_to_face(0); // Go to main face after saving settings +} + +bool finetune_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + + (void) settings; + (void) context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + // Show your initial UI here. + finetune_update_display(); + break; + + case EVENT_TICK: + // If needed, update your display here, at canonical 0.5sec position. + // We flash green LED once per minute to measure clock error, when we are not on first screen + if (finetune_page!=0) { + watch_date_time date_time; + date_time = watch_rtc_get_date_time(); + if (date_time.unit.second == 0) { + watch_set_led_green(); + #ifndef __EMSCRIPTEN__ + delay_us(500); + #endif + watch_set_led_off(); + } + } + + finetune_update_display(); + break; + + case EVENT_MODE_BUTTON_UP: + // Only allow for fast exit when correction is 0!!! + if (finetune_page == 0 && total_adjustment == 0) { + movement_move_to_next_face(); + } else { + finetune_page = (finetune_page + 1) % 3; + finetune_update_display(); + } + break; + + case EVENT_MODE_LONG_PRESS: + // You shouldn't need to change this case; Mode almost always moves to the next watch face. + finetune_page = (finetune_page + 1) % 3; + finetune_update_display(); + break; + + case EVENT_LIGHT_LONG_PRESS: + // We are making it slower by 250ms + if (finetune_page == 0) { + finetune_adjust_subseconds(250); + } else if (finetune_page == 2 && finetune_get_hours_passed() >= 6) { + // Applying ppm correction, only if >6 hours passed + nanosec_state.freq_correction += (int)round(finetune_get_correction() * 100); + finetune_update_correction_time(); + } + break; + + case EVENT_LIGHT_BUTTON_UP: + // We are making it slower by 25ms + if (finetune_page == 0) { + finetune_adjust_subseconds(25); + } + break; + + case EVENT_ALARM_LONG_PRESS: + if (finetune_page == 0) { + finetune_adjust_subseconds(750); + } else if (finetune_page == 2) { + // Exit without applying correction to ppm, but update correction time + finetune_update_correction_time(); + } + break; + + case EVENT_ALARM_BUTTON_UP: + if (finetune_page == 0) { + finetune_adjust_subseconds(975); + } + 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: + if (total_adjustment == 0) // Timeout only works if no adjustment was made + movement_move_to_face(0); + break; + + case EVENT_LOW_ENERGY_UPDATE: + // If you did not resign in EVENT_TIMEOUT, you can use this event to update the display once a minute. + // Avoid displaying fast-updating values like seconds, since the display won't update again for 60 seconds. + // You should also consider starting the tick animation, to show the wearer that this is sleep mode: + // watch_start_tick_animation(500); + break; + + default: + break; + } + + // return true if the watch can enter standby mode. If you are PWM'ing an LED or buzzing the buzzer here, + // you should return false since the PWM driver does not operate in standby mode. + return true; +} + +void finetune_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + + if (total_adjustment != 0) { + finetune_update_correction_time(); + } +} |