diff --git a/movement/alt_fw/jmm.h b/movement/alt_fw/jmm.h index 878b4a4..5c2cee7 100644 --- a/movement/alt_fw/jmm.h +++ b/movement/alt_fw/jmm.h @@ -10,6 +10,7 @@ const watch_face_t watch_faces[] = { simple_clock_face, stock_stopwatch_face, world_clock_face, + metric_face, totp_face_lfs, moon_phase_face, sunrise_sunset_face, diff --git a/movement/make/Makefile b/movement/make/Makefile index 576822c..cf8850a 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -131,6 +131,7 @@ SRCS += \ ../watch_faces/clock/minute_repeater_decimal_face.c \ ../watch_faces/complication/tuning_tones_face.c \ ../watch_faces/complication/kitchen_conversions_face.c \ + ../watch_faces/complication/metric_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 3557110..17fe809 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -104,6 +104,7 @@ #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" #include "kitchen_conversions_face.h" +#include "metric_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/equinox.c b/movement/watch_faces/complication/equinox.c new file mode 100644 index 0000000..e8d862e --- /dev/null +++ b/movement/watch_faces/complication/equinox.c @@ -0,0 +1,101 @@ +#define D2R(a) ((a)*(M_PI/180.)) + + + + +// shamelessly stolen from Meeus Astronmical Algorithms Chapter 27 table 27.B +// chapter 25 is probably more tractable, but this is easier to code up + +# if 0 +static double mean_vernal_equinox (unsigned year) +{ + double y = year; + y -= 2000; + y *= 0.001; + return 2451623.80984 + 365242.37404 * y + 0.05169 * (y * y) - 0.00411 * (y * y * y) - 0.00057 * (y * y * y * y); +} + +static double mean_summer_solstice (unsigned year) +{ + double y = year; + y -= 2000; + y *= 0.001; + return 2451716.56767 + 365241.62603 * y + 0.00325 * (y * y) + 0.00888 * (y * y * y) - 0.00030 * (y * y * y * y); +} +#endif + +static double mean_autumnal_equinox (unsigned year) +{ + double y = year; + y -= 2000; + y *= 0.001; + return 2451810.21715 + 365242.01767 * y - 0.11575 * (y * y) + 0.00337 * (y * y * y) + 0.00078 * (y * y * y * y); +} + +#if 0 +static double mean_winter_solstice (unsigned year) +{ + double y = year; + y -= 2000; + y *= 0.001; + return 2451900.05952 + 365242.74049 * y - 0.06223 * (y * y) - 0.00823 * (y * y * y) + 0.00032 * (y * y * y * y); +} +#endif + +static double orbital_periodic_terms (double t) +{ +#define N 24 + const double A[N] = {485, 203, 199, 182, 156, 136, 77, 74, 70, 58, 52, 50, 45, + 44, 29, 18, 17, 16, 14, 12, 12, 12, 9, 8 + }; + const double B[N] = {D2R (324.96), D2R (337.23), D2R (342.08), D2R (27.85), + D2R (73.14), D2R (171.52), D2R (222.54), D2R (296.72), + D2R (243.58), D2R (119.81), D2R (297.17), D2R (21.02), + D2R (247.54), D2R (325.15), D2R (60.93), D2R (155.12), + D2R (288.79), D2R (198.04), D2R (199.76), D2R (95.39), + D2R (287.11), D2R (320.81), D2R (227.73), D2R (15.45) + }; + const double C[N] = {D2R (1934.136), D2R (32964.467), D2R (20.186), + D2R (445267.112), D2R (45036.886), D2R (22518.443), + D2R (65928.934), D2R (3034.906), D2R (9037.513), + D2R (33718.147), D2R (150.678), D2R (2281.226), + D2R (29929.562), D2R (31555.956), D2R (4443.417), + D2R (67555.328), D2R (4562.452), D2R (62894.029), + D2R (31436.921), D2R (14577.848), D2R (31931.756), + D2R (34777.259), D2R (1222.114), D2R (16859.074) + }; + double s = 0; + unsigned i; + + for (i = 0; i < N; ++i) + s += A[i] * cos (B[i] + (C[i] * t)); + + return s; +} + + +static double mean_to_real (double j0) +{ + + double t = (j0 - 2451545.) / 36525.; + double w = D2R ((35999.373 * t) - 2.47); + double dl = 1 + 0.0334 * cos (w) + 0.0007 * cos (2. * w); + double s = orbital_periodic_terms (t); + +#if 0 + printf ("j0=%.6f\r\n", j0); + printf ("t=%.6f\r\n", t); + printf ("w=%.6f\r\n", w); + printf ("dl=%.6f\r\n", dl); + printf ("s=%.6f\r\n", s); +#endif + + return j0 + ((0.00001 * s) / dl); +} + +static double autumnal_equinox (unsigned y) +{ + return mean_to_real (mean_autumnal_equinox (y)); + // return mean_to_real (mean_summer_solstice (y)); +} + diff --git a/movement/watch_faces/complication/metric_face.c b/movement/watch_faces/complication/metric_face.c new file mode 100644 index 0000000..9eaafbf --- /dev/null +++ b/movement/watch_faces/complication/metric_face.c @@ -0,0 +1,198 @@ +#include +#include +#include +#include "watch.h" +#include "watch_utility.h" +#include "vsop87a_micro.h" // smaller size, less accurate +#include "vsop87a_milli.h" +#include "astrolib.h" +#include "metric_face.h" + +#define RATIO 8 + +#include "equinox.c" + +static const char *days[10] = + { "Pr", "Du", "Tr", "Qa", "Qi", "SH", "Sp", "Oc", "No", "De" }; + + +static uint32_t +jd_to_timestamp (double tjd) +{ + tjd -= 2440587.5; + tjd *= 86400.; + return (uint32_t) floor (tjd); +} + +static uint32_t +timestamp_start_of_year (int y) +{ + double julian_equinox = autumnal_equinox (y); + julian_equinox += (2.3372305555 / 360); /* Offset of paris meridian from greenwich meridian */ + return jd_to_timestamp (julian_equinox); +} + +static int +days_since_equinox (watch_date_time date_time) +{ + uint32_t now = watch_utility_date_time_to_unix_time (date_time, 0); + uint32_t start_of_year = + timestamp_start_of_year (date_time.unit.year + 2020); + + start_of_year/=86400; + start_of_year*=86400; + + if (start_of_year > now) + start_of_year = + timestamp_start_of_year (date_time.unit.year + 2020 - 1); + + now -= start_of_year; + + now /= 86400; + + return (int) now; +} + +static void +calculate_metric_date (metric_state_t * state, watch_date_time date_time) +{ + int dse = days_since_equinox (date_time); + + state->m_day = (dse % 30) + 1; + state->m_wday = (dse % 10); + state->c_day = date_time.unit.day; + +} + + +static uint32_t +date_time_to_ds (watch_date_time dt) +{ + uint32_t ret; + ret = dt.unit.hour; + ret *= 60; + ret += dt.unit.minute; + ret *= 60; + ret += dt.unit.second; + return ret; +} + +static void +_metric_face_sync (movement_settings_t * settings, metric_state_t * state) +{ + watch_date_time date_time = watch_rtc_get_date_time (); + + (void) settings; + + if (date_time.unit.day != state->c_day) + calculate_metric_date (state, date_time); + + state->dt = date_time_to_ds (date_time) * RATIO; + state->resync_at = state->dt * (3600 * RATIO); + if (state->resync_at > (86400 * RATIO)) + state->resync_at = (86400 * RATIO); + +} + +static void +_metric_face_update (movement_event_t event, movement_settings_t * settings, + metric_state_t * state, int show_secs) +{ + uint64_t v; + char buf[14]; + + (void) event; + + if (state->dt >= state->resync_at) + _metric_face_sync (settings, state); + + v = state->dt; + v *= (uint64_t) 100000; + v /= (uint64_t) (RATIO * 86400); + + +#if 0 + { + watch_date_time date_time = watch_rtc_get_date_time (); + printf ("%d %d\n", state->dt, date_time_to_ds (date_time) * RATIO); + } +#endif + + + sprintf (buf, "%s%2d %05d", days[state->m_wday], (int) state->m_day, + (int) v); + + if (!show_secs) + { + buf[8] = ' '; + buf[9] = ' '; + } + + watch_display_string (buf, 0); +} + +void +metric_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 (metric_state_t)); + memset (*context_ptr, 0, sizeof (metric_state_t)); + } +} + +void +metric_face_activate (movement_settings_t * settings, void *context) +{ + metric_state_t *state = (metric_state_t *) context; + (void) settings; + movement_request_tick_frequency (RATIO); + state->resync_at = 0; +} + +bool +metric_face_loop (movement_event_t event, movement_settings_t * settings, + void *context) +{ + (void) settings; + metric_state_t *state = (metric_state_t *) context; + + switch (event.event_type) + { + case EVENT_ACTIVATE: + watch_set_colon (); + /*fall through */ + case EVENT_TICK: + state->dt++; + _metric_face_update (event, settings, state, 1); + break; + case EVENT_LOW_ENERGY_UPDATE: + state->dt += RATIO * 60; + _metric_face_update (event, settings, state, 0); + break; + case EVENT_ALARM_BUTTON_UP: + break; + case EVENT_ALARM_LONG_PRESS: + break; + case EVENT_TIMEOUT: + state->resync_at = 0; + break; + default: + movement_default_loop_handler (event, settings); + break; + } + + return true; +} + +void +metric_face_resign (movement_settings_t * settings, void *context) +{ + (void) settings; + metric_state_t *state = (metric_state_t *) context; + state->resync_at = 0; + state->c_day = 0; +} diff --git a/movement/watch_faces/complication/metric_face.h b/movement/watch_faces/complication/metric_face.h new file mode 100644 index 0000000..1968e38 --- /dev/null +++ b/movement/watch_faces/complication/metric_face.h @@ -0,0 +1,51 @@ +/* + * 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 METRIC_FACE_H_ +#define METRIC_FACE_H_ + +#include "movement.h" + +typedef struct { + uint32_t dt; + uint32_t resync_at; + uint32_t m_day; + uint32_t m_wday; + uint32_t c_day; +} metric_state_t; + +void metric_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void metric_face_activate(movement_settings_t *settings, void *context); +bool metric_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void metric_face_resign(movement_settings_t *settings, void *context); + +#define metric_face ((const watch_face_t){ \ + metric_face_setup, \ + metric_face_activate, \ + metric_face_loop, \ + metric_face_resign, \ + NULL, \ +}) + +#endif // METRIC_FACE_H_