/* * 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 #include #include #include "watch_utility.h" #include "solstice_face.h" // Find solstice or equinox time in JDE for a given year, via method from Meeus Ch 27 static double calculate_solstice_equinox(uint16_t year, uint8_t k) { double Y = ((double)year - 2000) / 1000; double approx_terms[4][5] = { {2451623.80984, 365242.37404, 0.05169, -0.00411, -0.00057}, // March equinox {2451716.56767, 365241.62603, 0.00325, 0.00888, -0.00030}, // June solstice {2451810.21715, 365242.01767, -0.11575, 0.00337, 0.00078}, // September equinox {2451900.05952, 365242.74049, -0.06223, -0.00823, 0.00032}, // December solstice }; double JDE0 = approx_terms[k][0] + Y * (approx_terms[k][1] + Y * (approx_terms[k][2] + Y * (approx_terms[k][3] + Y * approx_terms[k][4]))); double T = (JDE0 - 2451545.0) / 36525; double W = 35999.373 * T - 2.47; double dlambda = 1 + (0.0334 * cos(W * M_PI / 180.0)) + (0.0007 * cos(2 * W * M_PI / 180.0)); double correction_terms[25][3] = { {485,324.96,1934.136}, {203,337.23,32964.467}, {199,342.08,20.186}, {182,27.85,445267.112}, {156,73.14,45036.886}, {136,171.52,22518.443}, {77,222.54,65928.934}, {74,296.72,3034.906}, {70,243.58,9037.513}, {58,119.81,33718.147}, {52,297.17,150.678}, {50,21.02,2281.226}, {45,247.54,29929.562}, {44,325.15,31555.956}, {29,60.93,4443.417}, {18,155.12,67555.328}, {17,288.79,4562.452}, {16,198.04,62894.029}, {14,199.76,31436.921}, {12,95.39,14577.848}, {12,287.11,31931.756}, {12,320.81,34777.259}, {9,227.73,1222.114}, {8,15.45,16859.074}, }; double S = 0; for (int i = 0; i < 25; i++) { S += correction_terms[i][0] * cos((correction_terms[i][1] + correction_terms[i][2] * T) * M_PI / 180.0); } double JDE = JDE0 + (0.00001 * S) / dlambda; return JDE; } // Convert JDE to Gergorian datetime as per Meeus Ch 7 static watch_date_time jde_to_date_time(double JDE) { double tmp = JDE + 0.5; double Z = floor(tmp); double F = fmod(tmp, 1); double A; if (Z < 2299161) { A = Z; } else { double alpha = floor((Z - 1867216.25) / 36524.25); A = Z + 1 + alpha - floor(alpha / 4); } double B = A + 1524; double C = floor((B - 122.1) / 365.25); double D = floor(365.25 * C); double E = floor((B - D) / 30.6001); double day = B - D - floor(30.6001 * E) + F; double month; if (E < 14) { month = E - 1; } else { month = E - 13; } double year; if (month > 2) { year = C - 4716; } else { year = C - 4715; } double hours = fmod(day, 1) * 24; double minutes = fmod(hours, 1) * 60; double seconds = fmod(minutes, 1) * 60; watch_date_time result = {.unit = { floor(seconds), floor(minutes), floor(hours), floor(day), floor(month), floor(year - 2020) }}; return result; } static void calculate_datetimes(solstice_state_t *state, movement_settings_t *settings) { for (int i = 0; i < 4; i++) { // TODO: handle DST changes state->datetimes[i] = jde_to_date_time(calculate_solstice_equinox(2020 + state->year, i) + (movement_timezone_offsets[settings->bit.time_zone] / (60.0*24.0))); } } void solstice_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { (void) settings; (void) watch_face_index; if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(solstice_state_t)); solstice_state_t *state = (solstice_state_t *)*context_ptr; watch_date_time now = watch_rtc_get_date_time(); state->year = now.unit.year; state->index = 0; calculate_datetimes(state, settings); uint32_t now_unix = watch_utility_date_time_to_unix_time(now, 0); for (int i = 0; i < 4; i++) { if (state->index == 0 && watch_utility_date_time_to_unix_time(state->datetimes[i], 0) > now_unix) { state->index = i; } } } } void solstice_face_activate(movement_settings_t *settings, void *context) { (void) settings; (void) context; } static void show_main_screen(solstice_state_t *state) { char buf[11]; watch_date_time date_time = state->datetimes[state->index]; sprintf(buf, " %2d %2d%02d", date_time.unit.year + 20, date_time.unit.month, date_time.unit.day); watch_display_string(buf, 0); } static void show_date_time(movement_settings_t *settings, solstice_state_t *state) { char buf[11]; watch_date_time date_time = state->datetimes[state->index]; if (!settings->bit.clock_mode_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; } 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_set_colon(); watch_display_string(buf, 0); } bool solstice_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { solstice_state_t *state = (solstice_state_t *)context; switch (event.event_type) { case EVENT_ALARM_LONG_PRESS: show_date_time(settings, state); break; case EVENT_LIGHT_BUTTON_UP: if (state->index == 0) { if (state->year == 0) { break; } state->year--; state->index = 3; calculate_datetimes(state, settings); } else { state->index--; } show_main_screen(state); break; case EVENT_ALARM_BUTTON_UP: state->index++; if (state->index > 3) { if (state->year == 83) { break; } state->year++; state->index = 0; calculate_datetimes(state, settings); } show_main_screen(state); break; case EVENT_ALARM_LONG_UP: watch_clear_colon(); watch_clear_indicator(WATCH_INDICATOR_PM); show_main_screen(state); break; case EVENT_ACTIVATE: show_main_screen(state); break; case EVENT_TIMEOUT: movement_move_to_face(0); break; default: return movement_default_loop_handler(event, settings); } return true; } void solstice_face_resign(movement_settings_t *settings, void *context) { (void) settings; (void) context; }