diff --git a/movement/make/Makefile b/movement/make/Makefile index c294b068..2e623e6b 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -121,6 +121,7 @@ SRCS += \ ../watch_faces/complication/couch_to_5k_face.c \ ../watch_faces/clock/minute_repeater_decimal_face.c \ ../watch_faces/complication/tuning_tones_face.c \ + ../watch_faces/complication/periodic_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 cb58612f..23c5c354 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -98,6 +98,7 @@ #include "couch_to_5k_face.h" #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" +#include "periodic_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/periodic_face.c b/movement/watch_faces/complication/periodic_face.c new file mode 100644 index 00000000..6746c26c --- /dev/null +++ b/movement/watch_faces/complication/periodic_face.c @@ -0,0 +1,357 @@ +/* + * MIT License + * + * Copyright (c) 2023 PrimmR + * + * 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 "periodic_face.h" + +void periodic_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(periodic_state_t)); + memset(*context_ptr, 0, sizeof(periodic_state_t)); + } +} + +void periodic_face_activate(movement_settings_t *settings, void *context) +{ + (void)settings; + periodic_state_t *state = (periodic_state_t *)context; + + state->atomic_num = 1; + state->mode = 0; + state->selection_index = 0; + + movement_request_tick_frequency(2); +} + +typedef struct +{ + char name[3]; + uint16_t atomic_mass; + char group[3]; +} element; + +// Comments on the table denote symbols that cannot be displayed +#define MAX_ELEMENT 118 +const element table[MAX_ELEMENT] = { + {"H ", 1, " "}, + {"He", 4, " 0"}, + {"Li", 7, " 1"}, + {"Be", 9, " 2"}, + {"B ", 11, " 3"}, + {"C ", 12, " 4"}, + {"N ", 14, " 5"}, + {"O ", 16, " 6"}, + {"F ", 19, " 7"}, + {"Ne", 20, " 0"}, + {"Na", 23, " 1"}, + {"Mg", 24, " 2"}, // + {"Al", 27, " 3"}, + {"Si", 28, " 4"}, + {"P ", 31, " 5"}, + {"S ", 32, " 6"}, + {"Cl", 355, " 7"}, + {"Ar", 40, " 0"}, + {"K ", 39, " 1"}, + {"Ca", 40, " 2"}, + {"Sc", 45, " T"}, + {"Ti", 48, " T"}, + {" W", 51, " T"}, // "V" + {"Cr", 52, " T"}, + {"Mn", 55, " T"}, + {"Fe", 56, " T"}, + {"Co", 59, " T"}, + {"Ni", 59, " T"}, + {"Cu", 635, " T"}, + {"Zn", 65, " T"}, + {"Ga", 70, " 3"}, + {"Ge", 73, " 4"}, + {"As", 75, " 5"}, // + {"Se", 79, " 6"}, + {"Br", 80, " 7"}, + {"Kr", 84, " 0"}, + {"Rb", 85, " 1"}, + {"Sr", 88, " 2"}, + {"Y ", 89, " T"}, + {"Zr", 91, " T"}, + {"Nb", 93, " T"}, + {"Mo", 96, " T"}, + {"Tc", 97, " T"}, + {"Ru", 101, " T"}, + {"Rh", 103, " T"}, + {"Pd", 106, " T"}, + {"Ag", 108, " T"}, // + {"Cd", 112, " T"}, + {"In", 115, " 3"}, + {"Sn", 119, " 4"}, + {"Sb", 122, " 5"}, + {"Te", 128, " 6"}, + {"I ", 127, " 7"}, + {"Xe", 131, " 0"}, + {"CS", 133, " 1"}, + {"Ba", 137, " 2"}, + {"La", 139, "1a"}, // La + {"Ce", 140, "1a"}, + {"Pr", 141, "1a"}, + {"Nd", 144, "1a"}, + {"Pm", 145, "1a"}, + {"Sm", 150, "1a"}, + {"Eu", 152, "1a"}, + {"Gd", 157, "1a"}, + {"Tb", 159, "1a"}, + {"Dy", 163, "1a"}, // .5 Rounded up due to space constraints + {"Ho", 165, "1a"}, + {"Er", 167, "1a"}, + {"Tm", 169, "1a"}, + {"Yb", 173, "1a"}, + {"Lu", 175, "1a"}, + {"Hf", 179, " T"}, + {"Ta", 181, " T"}, + {"W ", 184, " T"}, + {"Re", 186, " T"}, + {"OS", 190, " T"}, + {"Ir", 192, " T"}, + {"Pt", 195, " T"}, + {"Au", 197, " T"}, + {"Hg", 201, " T"}, // + {"Tl", 204, " 3"}, + {"Pb", 207, " 4"}, + {"Bi", 209, " 5"}, + {"Po", 209, " 6"}, + {"At", 210, " 7"}, + {"Rn", 222, " 0"}, + {"Fr", 223, " 1"}, + {"Ra", 226, " 2"}, + {"Ac", 227, "Ac"}, + {"Th", 232, "Ac"}, + {"Pa", 231, "Ac"}, + {"U ", 238, "Ac"}, + {"Np", 237, "Ac"}, // + {"Pu", 244, "Ac"}, + {"Am", 243, "Ac"}, + {"Cm", 247, "Ac"}, + {"Bk", 247, "Ac"}, // + {"Cf", 251, "Ac"}, + {"Es", 252, "Ac"}, // + {"Fm", 257, "Ac"}, + {"Md", 258, "Ac"}, + {"No", 259, "Ac"}, + {"Lr", 262, "Ac"}, + {"Rf", 267, " T"}, + {"Db", 262, " T"}, + {"Sg", 269, " T"}, // + {"Bh", 264, " T"}, + {"Hs", 269, " T"}, + {"Mt", 278, " T"}, + {"Ds", 281, " T"}, // + {"Rg", 282, " T"}, // + {"Cn", 285, " T"}, + {"Nh", 286, " 3"}, + {"Fl", 289, " 4"}, + {"Mc", 289, " 5"}, + {"LW", 293, " 6"}, // Lv + {"Ts", 294, " 7"}, // + {"Og", 294, " 0"}, // +}; + +// Warning light for symbols that can't be displayed +static void _warning(periodic_state_t *state) +{ + char second_char = table[state->atomic_num - 1].name[1]; + if (second_char == 'p' || second_char == 'g' || second_char == 'y' || second_char == 's') + { + watch_set_indicator(WATCH_INDICATOR_BELL); + } + else + { + watch_clear_indicator(WATCH_INDICATOR_BELL); + } +} + +// Regular mode display +static void _periodic_face_update_lcd(periodic_state_t *state) +{ + // Colon as a decimal for Cl & Cu + if (state->atomic_num == 17 || state->atomic_num == 29) + { + watch_set_colon(); + } + else + { + watch_clear_colon(); + } + + _warning(state); + + char buf[11]; + sprintf(buf, "%s%s%-3d%3d", table[state->atomic_num - 1].name, table[state->atomic_num - 1].group, table[state->atomic_num - 1].atomic_mass, state->atomic_num); + watch_display_string(buf, 0); +} + +// Selection mode logic +static void _periodic_face_selection_increment(periodic_state_t *state) +{ + uint8_t digit0 = (state->atomic_num / 100) % 10; + uint8_t digit1 = (state->atomic_num / 10) % 10; + uint8_t digit2 = (state->atomic_num) % 10; + + // Increment the selected digit by 1 + switch (state->selection_index) + { + case 0: + digit0 ^= 1; + break; + case 1: + if (digit0 == MAX_ELEMENT / 100 && digit1 == (MAX_ELEMENT / 10) % 10) + digit1 = 0; + else + digit1 = (digit1 + 1) % 10; + break; + case 2: + if (digit0 == MAX_ELEMENT / 100 && digit1 == (MAX_ELEMENT / 10) % 10 && digit2 == MAX_ELEMENT % 10) + digit2 = 0; + else + digit2 = (digit2 + 1) % 10; + break; + } + + // Prevent 000 + if (digit0 == 0 && digit1 == 0 && digit2 == 0) { + digit2 = 1; + } + + // Prevent Overflow + if (digit0 == (MAX_ELEMENT / 100) % 10 && digit1 > (MAX_ELEMENT / 10) % 10) + { + digit2 = MAX_ELEMENT % 10; + digit1 = (MAX_ELEMENT / 10) % 10; + } + + state->atomic_num = digit0 * 100 + digit1 * 10 + digit2; +} + +// Selection mode display +static void _periodic_face_selection(periodic_state_t *state, uint8_t subsec) +{ + uint8_t digit0 = (state->atomic_num / 100) % 10; + uint8_t digit1 = (state->atomic_num / 10) % 10; + uint8_t digit2 = (state->atomic_num) % 10; + + watch_display_string(" ", 0); + + char buf[2] = {'\0'}; + + buf[0] = (state->selection_index == 0 && subsec == 0) ? ' ' : digit0 + '0'; + watch_display_string(buf, 5); + + buf[0] = (state->selection_index == 1 && subsec == 0) ? ' ' : digit1 + '0'; + watch_display_string(buf, 6); + + buf[0] = (state->selection_index == 2 && subsec == 0) ? ' ' : digit2 + '0'; + watch_display_string(buf, 7); + + char buf2[3]; + sprintf(buf2, "%s", table[state->atomic_num - 1].name); + watch_display_string(buf2, 0); + + _warning(state); +} + +bool periodic_face_loop(movement_event_t event, movement_settings_t *settings, void *context) +{ + periodic_state_t *state = (periodic_state_t *)context; + + switch (event.event_type) + { + case EVENT_ACTIVATE: + _periodic_face_update_lcd(state); + break; + case EVENT_TICK: + if (state->mode != 0) + { + _periodic_face_selection(state, event.subsecond % 2); + } + break; + case EVENT_LIGHT_BUTTON_UP: + // Only light LED when in regular mode + if (state->mode != MODE_VIEW) + { + state->selection_index = (state->selection_index + 1) % 3; + _periodic_face_selection(state, event.subsecond % 2); + } + break; + case EVENT_LIGHT_BUTTON_DOWN: + if (state->mode != MODE_SELECT) + movement_illuminate_led(); + break; + case EVENT_ALARM_BUTTON_UP: + if (state->mode == MODE_VIEW) + { + state->atomic_num = (state->atomic_num % MAX_ELEMENT) + 1; // Wraps back to 1 + _periodic_face_update_lcd(state); + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_C7, 50); + } + else + { + _periodic_face_selection_increment(state); + _periodic_face_selection(state, event.subsecond % 2); + } + break; + case EVENT_ALARM_LONG_PRESS: + // Toggle between selection mode and regular + if (state->mode == MODE_VIEW) + { + state->mode = MODE_SELECT; + _periodic_face_selection(state, event.subsecond % 2); + } + else + { + state->mode = MODE_VIEW; + _periodic_face_update_lcd(state); + } + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_C8, 50); + + break; + case EVENT_TIMEOUT: + break; + case EVENT_LOW_ENERGY_UPDATE: + break; + default: + return movement_default_loop_handler(event, settings); + } + + return true; +} + +void periodic_face_resign(movement_settings_t *settings, void *context) +{ + (void)settings; + (void)context; + + // handle any cleanup before your watch face goes off-screen. +} diff --git a/movement/watch_faces/complication/periodic_face.h b/movement/watch_faces/complication/periodic_face.h new file mode 100644 index 00000000..56476ca1 --- /dev/null +++ b/movement/watch_faces/complication/periodic_face.h @@ -0,0 +1,61 @@ +/* + * MIT License + * + * Copyright (c) 2023 PrimmR + * + * 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 PERIODIC_FACE_H_ +#define PERIODIC_FACE_H_ + +#include "movement.h" + +/* + * Periodic Table Face + * + * Elements can be viewed sequentially with a short press of the alarm button, + * or the atomic number can be input directly after holding down the alarm button. + * + */ + +#define MODE_VIEW 0 +#define MODE_SELECT 1 + +typedef struct { + uint8_t atomic_num; + uint8_t mode; + uint8_t selection_index; +} periodic_state_t; + +void periodic_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void periodic_face_activate(movement_settings_t *settings, void *context); +bool periodic_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void periodic_face_resign(movement_settings_t *settings, void *context); + +#define periodic_face ((const watch_face_t){ \ + periodic_face_setup, \ + periodic_face_activate, \ + periodic_face_loop, \ + periodic_face_resign, \ + NULL, \ +}) + +#endif // PERIODIC_FACE_H_ +