Files
Sensor-Watch/watch-faces/settings/finetune_face.c
T
Tim Douglas b178390e75 Fix ppm fractional display on finetune Frq page
The Frq page used remainderf to extract the fractional part of the
correction value, but remainderf rounds to the nearest integer and
returns negative results when the fraction exceeds 0.5. This caused
values like 0.965 ppm to render as " 0-350" instead of " 09650".
Switch to fmodf, matching the DELtA page above.
2026-05-26 23:15:06 -04:00

236 lines
8.6 KiB
C

/*
* 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.
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "finetune_face.h"
#include "nanosec_face.h"
#include "delay.h"
extern nanosec_state_t nanosec_state;
int total_adjustment;
int8_t finetune_page;
void finetune_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(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(void *context) {
(void) context;
// Handle any tasks related to your watch face coming on screen.
watch_display_text(WATCH_POSITION_TOP_LEFT, "FT");
total_adjustment = 0;
finetune_page = 0;
}
static float finetune_get_hours_passed(void) {
uint32_t current_time = movement_get_utc_timestamp();
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_text_with_fallback(WATCH_POSITION_TOP_LEFT, "FTU", "FT");
watch_date_time_t date_time = movement_get_utc_date_time();
sprintf(buf, "%04d%02d", abs(total_adjustment), date_time.unit.second);
watch_display_text(WATCH_POSITION_BOTTOM, buf);
if (total_adjustment < 0) {
watch_display_text(WATCH_POSITION_TOP_RIGHT, "--");
} else {
watch_display_text(WATCH_POSITION_TOP_RIGHT, " ");
}
} else if (finetune_page == 1) {
float hours = finetune_get_hours_passed();
watch_display_text(WATCH_POSITION_TOP_RIGHT, " ");
watch_display_text_with_fallback(WATCH_POSITION_TOP, "DELtA", "DT");
sprintf(buf, "%4d%02d", (int)hours, (int)(fmodf(hours, 1.) * 100));
watch_display_text(WATCH_POSITION_BOTTOM, buf);
} else if (finetune_page == 2) {
watch_display_text(WATCH_POSITION_TOP_RIGHT, " ");
watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "Frq", " F");
if (finetune_get_hours_passed() < 6) {
watch_display_text(WATCH_POSITION_BOTTOM, "6HR ");
} else {
float correction = finetune_get_correction();
watch_display_text(WATCH_POSITION_TOP_RIGHT, (total_adjustment < 0) ? " -" : " ");
sprintf(buf, "%2d%04d", (int)fabsf(correction), (int)(fmodf(fabsf(correction), 1.) * 10000));
watch_display_text(WATCH_POSITION_BOTTOM, buf);
}
}
}
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) {
uint32_t timestamp = movement_get_utc_timestamp();
timestamp += 1;
movement_set_utc_timestamp(timestamp);
}
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 = movement_get_utc_timestamp();
nanosec_save();
movement_move_to_face(0); // Go to main face after saving settings
}
bool finetune_face_loop(movement_event_t event, void *context) {
(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_t date_time;
date_time = movement_get_utc_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_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 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(void *context) {
(void) context;
if (total_adjustment != 0) {
finetune_update_correction_time();
}
}