From 721887de3a94ee8e7c87ed3ed0235bacf20ffc59 Mon Sep 17 00:00:00 2001 From: randogoth Date: Sun, 16 Apr 2023 15:16:10 +0100 Subject: [PATCH 1/4] Coin & Dice Toss & Geomantic Divination Watch Faces (#235) * init * advanced latlon setting * simple functionality done * lat lon high precision fwd bwd * edit toggle * added readme for branch * DD DMS conversion & cleanup * DD to OLC conversion * olc encoding & decoding * OLC implementation * swapped bools for modes, code cleanup * place name editor * updated button logic, fixed display * load and save places in state array * todo list * simplified OLC functions * geohash conversion functions * geohash display & digit functions * todo * finished geohash implementation * code display function, defaults, bugfixes * read/write file/reg logic * long light in DATA to cancel * write to registry * todo * read & write backup register * file read/write * todo * new more concise button logic, optimizations * todo * renamed & cleaned up, fixed button logic * documentation * documentation * LAP mode for all coordinate screens * faster and more precise geohash algorithm * updated description * updated docu * simple place face * bugfixes, updated documentation * init * meh * added public functions for OLC and Geohash * randonauting face * fix * display fix * cleanup * bugfixes * bugfix * added place * fixed TRNG call * fixed declaration conflict * modulo bias filter * simplified things, chance RNG selection * fixed button logic, better menus * cleanup * documentation * docu fixes * init * basic functions * all needed static functions done * progress * coins and dice done * progress * place update * divination faces functionality done * better divine_bit * figure numbers and names * captions optional * coin animation * dice animation & optimizations * animation * changed names, documented * bugfix * cleanup * reset config --------- Co-authored-by: joeycastillo --- movement/make/Makefile | 2 + movement/movement_faces.h | 2 + .../watch_faces/complication/geomancy_face.c | 356 ++++++++ .../watch_faces/complication/geomancy_face.h | 99 +++ .../watch_faces/complication/toss_up_face.c | 790 ++++++++++++++++++ .../watch_faces/complication/toss_up_face.h | 112 +++ 6 files changed, 1361 insertions(+) create mode 100644 movement/watch_faces/complication/geomancy_face.c create mode 100644 movement/watch_faces/complication/geomancy_face.h create mode 100644 movement/watch_faces/complication/toss_up_face.c create mode 100644 movement/watch_faces/complication/toss_up_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index 2d6584f..d0d5398 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -111,6 +111,8 @@ SRCS += \ ../watch_faces/complication/invaders_face.c \ ../watch_faces/clock/world_clock2_face.c \ ../watch_faces/complication/time_left_face.c \ + ../watch_faces/complication/toss_up_face.c \ + ../watch_faces/complication/geomancy_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 358333a..a75deae 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -87,6 +87,8 @@ #include "invaders_face.h" #include "world_clock2_face.h" #include "time_left_face.h" +#include "toss_up_face.h" +#include "geomancy_face.h" #include "dual_timer_face.h" // New includes go above this line. diff --git a/movement/watch_faces/complication/geomancy_face.c b/movement/watch_faces/complication/geomancy_face.c new file mode 100644 index 0000000..689d4e1 --- /dev/null +++ b/movement/watch_faces/complication/geomancy_face.c @@ -0,0 +1,356 @@ +/* + * MIT License + * + * Copyright (c) 2023 Tobias Raayoni Last / @randogoth + * + * 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 "toss_up_face.h" +#include "geomancy_face.h" + +// CONSTANTS ////////////////////////////////////////////////////////////////// + +// The Bagua 八卦 Trigrams encoded as 3bit tribbles, represented as binary integer +static const uint32_t bagua = 0b00000101001110010111011100000000; + +// The King Wen Sequence 文王卦序 of the I Ching 易經 Hexagrams 卦 encoded as an array +// of decimal integers in the order of two combined Trigram tribbles from 0b000000 to +// 0b111111 +static const uint8_t wen_order[] = { + 1, 22, 7, 19, 15, 34, 44, 11, + 14, 51, 38, 52, 61, 55, 30, 32, + 6, 3, 28, 58, 39, 63, 46, 5, + 45, 17, 47, 56, 31, 49, 27, 43, + 23, 26, 2, 41, 50, 20, 16, 24, + 35, 21, 62, 36, 54, 29, 48, 12, + 18, 40, 59, 60, 53, 37, 57, 9, + 10, 25, 4, 8, 33, 13, 42, 0 +}; + +// The geomantic figures encoded as 4 bit nibbles, represented as hexadecimal integer +static const uint64_t geomantic = 0x4ABF39D25E76C180; + +// Abbreviations of the Names of the Geomantic Figures in the order of the 4 bit nibbles +// from 0b0000 to 0b1111 +static const char figures[16][2] = { + "VI" /* Via */, "Hd" /* Head of the Dragon */, "PA" /* Puella */, "GF" /* Greater Fortune*/, + "PR" /* Puer */, "AQ" /* Acquisitio */, "CA" /* Carcer */, "TR" /* Tristitia */, + "Td" /* Tail of the Dragon */, "CO" /* Conjunctio */, "AM" /* Amissio */, "AL" /* Albus */, + "LF" /* Lesser Fortune */, "RU" /* Rubeus */, "LA" /* Laetitia */, "PO" /* Populus */ +}; + +// DECLARATIONS /////////////////////////////////////////////////////////////// + +static void geomancy_face_display(); +static nibble_t _geomancy_pick_figure(); +static tribble_t _iching_pick_trigram(); +static uint8_t _iching_form_hexagram(); +static void _geomancy_display(nibble_t code); +static void _display_hexagram(uint8_t hexagram, char* str); +static void _fix_broken_line(uint8_t hexagram); +static void _throw_animation(geomancy_state_t *state); + +// WATCH FACE FUNCTIONS /////////////////////////////////////////////////////// + +void geomancy_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(geomancy_state_t)); + memset(*context_ptr, 0, sizeof(geomancy_state_t)); + } +} + +void geomancy_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +bool geomancy_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + geomancy_state_t *state = (geomancy_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + state->animate = false; + state->animation = 0; + watch_display_string(" IChing", 0); + break; + case EVENT_TICK: + if ( state->animate ) { + state->animation = (state->animation + 1) % 39; + geomancy_face_display(state); + } + break; + case EVENT_LIGHT_BUTTON_DOWN: + break; + case EVENT_LIGHT_BUTTON_UP: + if ( state->animate ) break; + if ( state->mode <= 1 ) state->mode = 2; + else if ( state->mode >= 2 ) state->mode = 0; + geomancy_face_display(state); + break; + case EVENT_ALARM_BUTTON_UP: + if ( state->animate ) break; + switch ( state->mode ) { + case 0: + state->mode++; + case 1: + state->animate = true; + state->i_ching_hexagram = _iching_form_hexagram(); + break; + case 2: + state->mode++; + case 3: + state->animate = true; + state->geomantic_figure = _geomancy_pick_figure().bits; + break; + default: + break; + } + geomancy_face_display(state); + break; + case EVENT_ALARM_LONG_PRESS: + if ( state->animate ) break; + state->caption = !state->caption; + watch_display_string(" ", 0); + geomancy_face_display(state); + break; + default: + return movement_default_loop_handler(event, settings); + } + return true; +} + +void geomancy_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +// STATIC FUNCTIONS /////////////////////////////////////////////////////////// + +/** @brief display handler */ +static void geomancy_face_display(geomancy_state_t *state) { + char token[7] = {0}; + nibble_t figure = *((nibble_t*) &state->geomantic_figure); + switch ( state->mode ) { + case 0: + watch_display_string(" IChing", 0); + break; + case 1: + _throw_animation(state); + if ( !state->animate ) { + _display_hexagram(state->i_ching_hexagram, token); + watch_display_string(token, 4); + _fix_broken_line(state->i_ching_hexagram); + if (state->caption) { + sprintf(token, "%2d", wen_order[state->i_ching_hexagram] + 1); + watch_display_string(token, 2); + } + } + break; + case 2: + watch_display_string(" GeomCy", 0); + break; + case 3: + _throw_animation(state); + if ( !state->animate ) { + if ( state->caption ) { + sprintf(token, "%c%c", figures[state->geomantic_figure][0], figures[state->geomantic_figure][1]); + watch_display_string(token, 0); + } + _geomancy_display(figure); + } + break; + default: + break; + } +} + +/** @brief screen clearing animation between castings */ +static void _throw_animation(geomancy_state_t *state) { + movement_request_tick_frequency(16); + switch ( state->animation ) { + case 0: + watch_set_pixel(0, 22); + break; + case 1: + watch_set_pixel(2, 22); + watch_set_pixel(2, 23); + watch_clear_pixel(0, 22); + break; + case 2: + watch_set_pixel(1, 22); + watch_set_pixel(0, 23); + break; + case 3: + watch_set_pixel(2, 0); + watch_set_pixel(1, 0); + watch_set_pixel(2, 21); + watch_set_pixel(1, 21); + watch_clear_pixel(2, 22); + watch_clear_pixel(1, 22); + watch_clear_pixel(2, 23); + watch_clear_pixel(0, 23); + watch_clear_pixel(1, 23); + break; + case 4: + watch_set_pixel(1, 17); + watch_set_pixel(0, 20); + watch_set_pixel(2, 10); + watch_set_pixel(0, 1); + break; + case 5: + watch_clear_pixel(2, 21); + watch_clear_pixel(1, 21); + watch_clear_pixel(2, 0); + watch_clear_pixel(1, 0); + watch_clear_pixel(1, 20); + watch_clear_pixel(2, 20); + watch_clear_pixel(0, 21); + watch_clear_pixel(1, 1); + watch_clear_pixel(0, 0); + watch_clear_pixel(2, 1); + watch_set_pixel(2, 19); + watch_set_pixel(0, 19); + watch_set_pixel(1, 2); + watch_set_pixel(0, 2); + break; + case 6: + watch_clear_pixel(1, 17); + watch_clear_pixel(0, 20); + watch_clear_pixel(2, 10); + watch_clear_pixel(0, 1); + watch_set_pixel(2, 18); + watch_set_pixel(0, 18); + watch_set_pixel(2, 3); + watch_set_pixel(0, 4); + break; + case 7: + watch_clear_pixel(2, 19); + watch_clear_pixel(0, 19); + watch_clear_pixel(1, 18); + watch_clear_pixel(1, 19); + watch_clear_pixel(1, 2); + watch_clear_pixel(0, 2); + watch_clear_pixel(1, 3); + watch_clear_pixel(0, 3); + watch_clear_pixel(2, 2); + watch_set_pixel(1, 4); + watch_set_pixel(0, 5); + break; + case 8: + watch_clear_pixel(2, 18); + watch_clear_pixel(0, 18); + watch_clear_pixel(2, 3); + watch_clear_pixel(0, 4); + watch_set_pixel(2, 5); + watch_set_pixel(1, 6); + break; + case 9: + watch_clear_pixel(1, 4); + watch_clear_pixel(0, 5); + watch_clear_pixel(1, 5); + watch_clear_pixel(2, 4); + watch_clear_pixel(0, 6); + break; + case 10: + watch_clear_pixel(2, 5); + watch_clear_pixel(1, 6); + break; + case 11: + state->animate = false; + state->animation = 0; + movement_request_tick_frequency(1); + break; + } +} + +// I CHING FUNCTIONS ////////////////////////////////////////////////////////// + +/** @brief form a trigram from three random bit picks + */ +static tribble_t _iching_pick_trigram() { + uint8_t index = (divine_bit() << 2) | (divine_bit() << 1) | divine_bit(); + tribble_t trigram = {(bagua >> (3 * index)) & 0b111}; + return trigram; +} + +/** @brief form a hexagram from two trigrams + */ +static uint8_t _iching_form_hexagram() { + tribble_t inner = _iching_pick_trigram(); + tribble_t outer = _iching_pick_trigram(); + uint8_t hexagram = (inner.bits << 3) | outer.bits; + return hexagram; +} + +/** @brief display hexagram + * @details | for unbroken lines and Ξ for broken lines, left of display is bottom + */ +static void _display_hexagram(uint8_t hexagram, char* str) { + str[6] = '\0'; // Null-terminate the string + for (uint8_t i = 0; i < 6; i++) { + if (hexagram & (1 << (5 - i))) { + str[i] = '1'; + } else { + str[i] = '='; + } + } +} + +/** @brief when Ξ digits show as = then manually add a line on top + */ +static void _fix_broken_line(uint8_t hexagram) { + for (uint8_t i = 0; i < 6; i++) { + if (!(hexagram & (1 << (5 - i)))) { + if ( i == 1 ) watch_set_pixel(2, 20); + if ( i == 3 ) watch_set_pixel(2, 1); + if ( i == 4 ) watch_set_pixel(2, 2); + if ( i == 5 ) watch_set_pixel(2, 4); + } + } +} + +// GEOMANCY FUNCTIONS ///////////////////////////////////////////////////////// + +/** @brief choose a geomantic figure from four random bits + * @details 0 represents · and 1 represents : counting from the bottom + */ +static nibble_t _geomancy_pick_figure() { + uint8_t index = (divine_bit() << 3) | (divine_bit() << 2) | (divine_bit() << 1) | divine_bit(); + nibble_t figure = {(geomantic >> (4 * (15 - index))) & 0xF}; + return figure; +} + +/** @brief display the geomantic figure, left of display is bottom + */ +static void _geomancy_display(nibble_t code) { + // draw geomantic figures + bool row1 = (code.bits >> 3) & 1; + bool row2 = (code.bits >> 2) & 1; + bool row3 = (code.bits >> 1) & 1; + bool row4 = code.bits & 1; + + if ( row1 ) watch_set_pixel(1, 18); else watch_set_pixel(1, 19); + if ( row2 ) { watch_set_pixel(2, 20); watch_set_pixel(0, 21);} else watch_set_pixel(1, 20); + if ( row3 ) watch_set_pixel(0, 22); else watch_set_pixel(1, 23); + if ( row4 ) { watch_set_pixel(2, 1); watch_set_pixel(0, 0);} else watch_set_pixel(1, 1); +} \ No newline at end of file diff --git a/movement/watch_faces/complication/geomancy_face.h b/movement/watch_faces/complication/geomancy_face.h new file mode 100644 index 0000000..4a19ba8 --- /dev/null +++ b/movement/watch_faces/complication/geomancy_face.h @@ -0,0 +1,99 @@ +/* + * MIT License + * + * Copyright (c) 2023 Tobias Raayoni Last / @randogoth + * + * 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 GEOMANCY_FACE_H_ +#define GEOMANCY_FACE_H_ + +#include "movement.h" + +/* + * GEOMANCY WATCH FACE + * + * A simple and straightforward watch face for the ancient Eastern geomantic divination system + * of I Ching and the western system of "Geomancy". It is an optional addition to the Toss Up + * Face. + * + * The LIGHT button toggles between the two systems of geomancy. + * + * The ALARM button casts an I Ching hexagram or Geomantic figure based on drawing virtual + * stalks from the True Random Number Generator in the Sensor Watch. + * + * The figures are flipped 90 degrees clockwise, so the left side is the bottom and the + * right side the top. + * + * LONG PRESSING ALARM toggles the display of the King Wen sequence index for the cast I Ching + * Hexagram (https://en.wikipedia.org/wiki/King_Wen_sequence )or the abbreviated name for the + * cast Geomantic Figure: + * + * GF - Greater Fortune (Fortuna Major) + * LF - Lesser Fortune (Fortuna Minor) + * PO - Populus + * VI - Via + * AL - Albus + * CO - Conjunctio + * PA - Puella + * AM - Amissio + * PR - Puer + * RU - Rubeus + * AQ - Acquisitio + * LA - Laetitia + * TR - Tristitia + * CA - Carcer + * HD - Head of the Dragon (Caput Draconis) + * TD - Tail of the Dragon (Cauda Draconis) + * + */ + +typedef struct { + uint8_t bits : 4; +} nibble_t; + +typedef struct { + uint8_t bits : 3; +} tribble_t; + +typedef struct { + uint8_t mode : 3; + uint8_t geomantic_figure; + uint8_t i_ching_hexagram : 6; + bool caption; + uint8_t animation; + bool animate; +} geomancy_state_t; + +void geomancy_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void geomancy_face_activate(movement_settings_t *settings, void *context); +bool geomancy_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void geomancy_face_resign(movement_settings_t *settings, void *context); + +#define geomancy_face ((const watch_face_t){ \ + geomancy_face_setup, \ + geomancy_face_activate, \ + geomancy_face_loop, \ + geomancy_face_resign, \ + NULL, \ +}) + +#endif // GEOMANCY_FACE_H_ + diff --git a/movement/watch_faces/complication/toss_up_face.c b/movement/watch_faces/complication/toss_up_face.c new file mode 100644 index 0000000..1923302 --- /dev/null +++ b/movement/watch_faces/complication/toss_up_face.c @@ -0,0 +1,790 @@ +/* + * MIT License + * + * Copyright (c) 2023 Tobias Raayoni Last / @randogoth + * + * 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 "toss_up_face.h" +#if __EMSCRIPTEN__ +#include +#else +#include "saml22j18a.h" +#endif + +static const char heads[] = { '8', 'h', '4', 'E', '(' }; +static const char tails[] = { '0', '+', 'N', '3', ')' }; +static const uint8_t dd[] = {2, 4, 6, 8, 10,12,20,24,30,32,36,48,99}; + +static void _roll_dice_multiple(char* result, uint8_t* dice, uint8_t num_dice); +static void _sort_coins(char* token, uint8_t num_bits, uint8_t bits, char* heads, char* tails); +void _display_coins(char* token, bool* bit_array, uint8_t length, toss_up_state_t *state); +static void _toss_up_face_display(toss_up_state_t *state); +static void _dice_animation(toss_up_state_t *state); +static void _coin_animation(toss_up_state_t *state); + +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////// + +void toss_up_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(toss_up_state_t)); + memset(*context_ptr, 0, sizeof(toss_up_state_t)); + toss_up_state_t *state = (toss_up_state_t *)*context_ptr; + + // defaults + state->coin_num = 1; + state->dice_num = 1; + state->dice_sides[0] = 6; + state->dice_sides[1] = 6; + state->dice_sides[2] = 6; + state->coin_style[0] = '8'; + state->coin_style[1] = '0'; + } +} + +void toss_up_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +bool toss_up_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + toss_up_state_t *state = (toss_up_state_t *)context; + uint8_t i = 0; + switch (event.event_type) { + case EVENT_ACTIVATE: + watch_display_string(" Coins ", 0); + break; + case EVENT_TICK: + if ( state->animate ) { + state->animation = (state->animation + 1); + _toss_up_face_display(state); + } + break; + case EVENT_LIGHT_BUTTON_DOWN: + break; + case EVENT_LIGHT_BUTTON_UP: + if ( state->animate ) break; + // change between coins and dice + if ( state->mode <= 1 ) state->mode = 2; + else if ( state->mode >= 2 ) state->mode = 0; + _toss_up_face_display(state); + break; + case EVENT_ALARM_BUTTON_UP: + // toss + if ( state->animate ) break; + switch (state->mode) { + case 0: + state->mode++; + case 1: + state->animate = true; + for (i = 0; i < state->coin_num; i++) { + state->coins[i] = divine_bit(); + } + break; + case 2: + state->mode++; + case 3: + state->animate = true; + for (i = 0; i < state->dice_num; i++) { + state->dice[i] = roll_dice(state->dice_sides[i]); + } + break; + default: + break; + } + _toss_up_face_display(state); + break; + case EVENT_LIGHT_LONG_PRESS: + if ( state->animate ) break; + state->animate = false; + switch (state->mode) { + case 0: // change to default coin style + state->coin_style[0] = heads[0]; + state->coin_style[1] = tails[0]; + state->coinface = 0; + break; + case 1: // change the coin style + state->coinface = (state->coinface + 1) % 5; + state->coin_style[0] = heads[state->coinface]; + state->coin_style[1] = tails[state->coinface]; + break; + case 2: // change to default dice sides + state->dice_sides[0] = 6; + state->dice_sides[1] = 6; + state->dice_sides[2] = 6; + state->dd = 0; + break; + case 3: // change the sides of the dice + state->dd = (state->dd + 1) % 13; + state->dice_sides[state->dice_num-1] = dd[state->dd]; + state->dice[state->dice_num-1] = dd[state->dd]; + break; + default: + break; + } + _toss_up_face_display(state); + break; + case EVENT_ALARM_LONG_PRESS: + if ( state->animate ) break; + state->animate = false; + switch (state->mode) { + case 0: // back to one coin + state->coin_num = 1; + break; + case 1: // up to 6 coins total + state->coin_num = (state->coin_num % 6) + 1; + break; + case 2: // back to one dice + state->dice_num = 1; + break; + case 3: // add up to 3 dice total + state->dice_num = (state->dice_num % 3) + 1; + state->dd = 0; + break; + default: + break; + } + _toss_up_face_display(state); + break; + default: + return movement_default_loop_handler(event, settings); + } + + return true; +} + +void toss_up_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +// STATIC FUNCTIONS /////////////////////////////////////////////////////////// + +/** @brief handles the display + */ +static void _toss_up_face_display(toss_up_state_t *state) { + char buf[11] = {0}; + char token[7] = {0}; + switch ( state->mode ) { + case 0: // coins title + sprintf(buf, " Coins "); + break; + case 1: // coins divination + _coin_animation(state); + if ( !state->animate ) { + watch_clear_display(); + _display_coins(token, state->coins, state->coin_num, state); + sprintf(buf, " %s", token); + } + break; + case 2: // dice title + sprintf(buf, " Dice "); + break; + case 3: // dice divination + _dice_animation(state); + if ( !state->animate ) { + _roll_dice_multiple(token, state->dice, state->dice_num + 1); + sprintf(buf, " %s", token); + } + break; + default: + break; + } + watch_display_string(buf, 0); +} + +/** @brief divination method to derive a bit from 32 TRNG bits + */ +uint8_t divine_bit(void) { + uint32_t stalks; + do { // modulo bias filter + stalks = get_true_entropy(); // get 32 TRNG bits as stalks + } while (stalks >= INT32_MAX || stalks <= 0); + + uint8_t pile1_xor = 0; + uint8_t pile2_xor = 0; + // Divide the stalks into two piles, alternating ends + for (uint8_t i = 0; i < 16; i++) { + uint8_t left_bit = (stalks >> (31 - 2*i)) & 1; + uint8_t right_bit = (stalks >> (30 - 2*i)) & 1; + if (i % 2 == 0) { + pile1_xor ^= left_bit; + pile2_xor ^= right_bit; + } else { + pile1_xor ^= right_bit; + pile2_xor ^= left_bit; + } + } + // Take the XOR of the pile results + uint8_t result_xor = pile1_xor ^ pile2_xor; + // Output 1 if result_xor is 1, 0 otherwise + return result_xor; +} + +/** @brief get 32 True Random Number bits + */ +uint32_t get_true_entropy(void) { + #if __EMSCRIPTEN__ + return rand() % INT32_MAX; + #else + hri_mclk_set_APBCMASK_TRNG_bit(MCLK); + hri_trng_set_CTRLA_ENABLE_bit(TRNG); + + while (!hri_trng_get_INTFLAG_reg(TRNG, TRNG_INTFLAG_DATARDY)); // Wait for TRNG data to be ready + + hri_trng_clear_CTRLA_ENABLE_bit(TRNG); + hri_mclk_clear_APBCMASK_TRNG_bit(MCLK); + return hri_trng_read_DATA_reg(TRNG); // Read a single 32-bit word from TRNG and return it + #endif +} + +// COIN FUNCTIONS ///////////////////////////////////////////////////////////// + +/** @brief sort tossed coins into a pile of heads and a pile of tails + */ +static void _sort_coins(char* token, uint8_t num_bits, uint8_t bits, char* heads, char* tails) { + uint8_t num_ones = 0; + for (uint8_t i = 0; i < num_bits; i++) { + if ((bits >> i) & 1) { + *token++ = *heads; + num_ones++; + } + } + if ( num_bits < 6 ) { + for (uint8_t i = 0; i < (6 - num_bits); i++) { + *token++ = ' '; + } + } + for (uint8_t i = 0; i < (num_bits - num_ones); i++) { + + *token++ = *tails; + } +} + +/** @brief convert bool array of coinflips to integer for sorting + */ +void _display_coins(char* token, bool* bit_array, uint8_t length, toss_up_state_t *state) { + uint8_t bits = 0; + for (uint8_t i = 0; i < length; i++) { + if (bit_array[i]) { + bits |= (1 << (length - 1 - i)); + } + } + _sort_coins(token, length, bits, &state->coin_style[0], &state->coin_style[1]); +} + +/** @brief coin animation + */ +static void _coin_animation(toss_up_state_t *state) { + bool heads = false; + bool tails = false; + for (uint8_t i = 0; i < state->coin_num; i++) { + if (state->coins[i] == true) { + heads++; + } else { + tails++; + } + } + movement_request_tick_frequency(32); + switch ( state->animation ) { + case 0: + watch_display_string(" ", 4); + if ( heads ) { + watch_set_pixel(0, 18); + watch_set_pixel(2, 18); + } else { + state->animation = 12; + } + break; + case 1: + if ( heads ) { + watch_set_pixel(1, 18); + } + break; + case 2: + if ( heads ) { + watch_set_pixel(0, 19); + watch_set_pixel(2, 19); + } + break; + case 3: + if ( heads ) { + watch_clear_pixel(0, 18); + watch_clear_pixel(2, 18); + } + break; + case 4: + if ( heads ) { + watch_clear_pixel(1, 18); + } + break; + case 5: + if ( heads ) { + watch_clear_pixel(0, 19); + watch_clear_pixel(2, 19); + watch_set_pixel(1, 17); + watch_set_pixel(0, 20); + } + break; + case 6: + if ( heads ) { + watch_set_pixel(2, 20); + watch_set_pixel(0, 21); + } + break; + case 7: + if ( heads ) { + watch_set_pixel(1, 21); + watch_set_pixel(2, 21); + } + break; + case 8: + if ( heads ) { + watch_clear_pixel(1, 17); + watch_clear_pixel(0, 20); + } + break; + case 9: + if ( heads ) { + watch_clear_pixel(2, 20); + watch_clear_pixel(0, 21); + } + break; + case 10: + if ( heads ) { + watch_clear_pixel(1, 21); + watch_clear_pixel(2, 21); + watch_set_pixel(1, 22); + watch_set_pixel(2, 22); + } + break; + case 11: + if ( heads ) { + watch_set_pixel(0, 22); + } + break; + case 12: + if ( heads ) { + watch_set_pixel(2, 23); + watch_set_pixel(0, 23); + } + if ( tails ) { + watch_set_pixel(0, 18); + watch_set_pixel(2, 18); + } + break; + case 13: + if ( heads ) { + watch_clear_pixel(1, 22); + watch_clear_pixel(2, 22); + } + if ( tails ) { + watch_set_pixel(1, 18); + } + break; + case 14: + if ( heads ) { + watch_clear_pixel(0, 22); + } + if ( tails ) { + watch_set_pixel(0, 19); + watch_set_pixel(2, 19); + } + break; + case 15: + if ( heads ) { + watch_clear_pixel(2, 23); + watch_clear_pixel(0, 23); + watch_set_pixel(2, 0); + watch_set_pixel(1, 0); + } + if ( tails ) { + watch_clear_pixel(0, 18); + watch_clear_pixel(2, 18); + } + break; + case 16: + if ( heads ) { + watch_set_pixel(2, 1); + watch_set_pixel(0, 0); + } + if ( tails ) { + watch_clear_pixel(1, 18); + } + break; + case 17: + if ( heads ) { + watch_set_pixel(2, 10); + watch_set_pixel(0, 1); + } + if ( tails ) { + watch_clear_pixel(0, 19); + watch_clear_pixel(2, 19); + watch_set_pixel(1, 17); + watch_set_pixel(0, 20); + } + break; + case 18: + if ( heads ) { + watch_clear_pixel(2, 0); + watch_clear_pixel(1, 0); + } + if ( tails ) { + watch_set_pixel(2, 20); + watch_set_pixel(0, 21); + } + break; + case 19: + if ( heads ) { + watch_clear_pixel(2, 1); + watch_clear_pixel(0, 0); + } + if ( tails ) { + watch_set_pixel(1, 21); + watch_set_pixel(2, 21); + } + break; + case 20: + if ( heads ) { + watch_set_pixel(2, 1); + watch_set_pixel(0, 0); + } + if ( tails ) { + watch_clear_pixel(1, 17); + watch_clear_pixel(0, 20); + } + break; + case 21: + if ( heads ) { + watch_set_pixel(2, 0); + watch_set_pixel(1, 0); + } + if ( tails ) { + watch_clear_pixel(2, 20); + watch_clear_pixel(0, 21); + } + break; + case 22: + if ( heads ) { + watch_clear_pixel(2, 10); + watch_clear_pixel(0, 1); + } + if ( tails ) { + watch_clear_pixel(1, 21); + watch_clear_pixel(2, 21); + watch_set_pixel(1, 22); + watch_set_pixel(2, 22); + } + break; + case 23: + if ( heads ) { + watch_clear_pixel(2, 1); + watch_clear_pixel(0, 0); + } + if ( tails ) { + watch_set_pixel(0, 22); + } + break; + case 24: + if ( heads ) { + watch_set_pixel(2, 23); + watch_set_pixel(0, 23); + watch_clear_pixel(2, 0); + watch_clear_pixel(1, 0); + } + if ( tails ) { + watch_set_pixel(2, 23); + watch_set_pixel(0, 23); + } + break; + case 25: + if ( heads ) { + watch_set_pixel(0, 22); + } + if ( tails ) { + watch_clear_pixel(1, 22); + watch_clear_pixel(2, 22); + } + break; + case 26: + if ( heads ) { + watch_set_pixel(1, 22); + watch_set_pixel(2, 22); + } + if ( tails ) { + watch_clear_pixel(0, 22); + } + break; + case 27: + if ( heads ) { + watch_clear_pixel(2, 23); + watch_clear_pixel(0, 23); + } + if ( tails ) { + watch_clear_pixel(2, 23); + watch_clear_pixel(0, 23); + watch_set_pixel(2, 0); + watch_set_pixel(1, 0); + } + break; + case 28: + if ( heads ) { + watch_clear_pixel(0, 22); + } + if ( tails ) { + watch_set_pixel(2, 1); + watch_set_pixel(0, 0); + } + break; + case 29: + if ( heads ) { + watch_set_pixel(1, 21); + watch_set_pixel(2, 21); + watch_clear_pixel(1, 22); + watch_clear_pixel(2, 22); + } + if ( tails ) { + watch_set_pixel(2, 10); + watch_set_pixel(0, 1); + } + break; + case 30: + if ( heads ) { + watch_set_pixel(2, 20); + watch_set_pixel(0, 21); + } + if ( tails ) { + watch_clear_pixel(1, 0); + watch_clear_pixel(2, 0); + } + break; + case 31: + if ( heads ) { + watch_set_pixel(1, 17); + watch_set_pixel(0, 20); + } + if ( tails ) { + watch_clear_pixel(2, 1); + watch_clear_pixel(0, 0); + } + break; + case 32: + if ( heads ) { + watch_clear_pixel(1, 21); + watch_clear_pixel(2, 21); + } + if ( tails ) { + watch_clear_pixel(2, 10); + watch_clear_pixel(0, 1); + watch_set_pixel(0, 2); + watch_set_pixel(1, 2); + } + break; + case 33: + if ( heads ) { + watch_clear_pixel(2, 20); + watch_clear_pixel(0, 21); + } + if ( tails ) { + watch_set_pixel(2, 2); + watch_set_pixel(0, 3); + } + break; + case 34: + if ( heads ) { + watch_set_pixel(0, 19); + watch_set_pixel(2, 19); + watch_clear_pixel(1, 17); + watch_clear_pixel(0, 20); + } + if ( tails ) { + watch_set_pixel(2, 3); + watch_set_pixel(0, 4); + } + break; + case 35: + if ( heads ) { + watch_set_pixel(1, 18); + } + if ( tails ) { + watch_clear_pixel(1, 2); + watch_clear_pixel(0, 2); + } + break; + case 36: + if ( heads ) { + watch_set_pixel(0, 18); + watch_set_pixel(2, 18); + } + if ( tails ) { + watch_clear_pixel(2, 2); + watch_clear_pixel(0, 3); + } + break; + case 37: + if ( heads ) { + watch_clear_pixel(0, 19); + watch_clear_pixel(2, 19); + } + if ( tails ) { + watch_clear_pixel(2, 3); + watch_clear_pixel(0, 4); + watch_set_pixel(1, 4); + watch_set_pixel(0, 5); + } + break; + case 38: + if ( heads ) { + watch_clear_pixel(1, 18); + } + if ( tails ) { + watch_set_pixel(2, 4); + watch_set_pixel(0, 6); + } + break; + case 39: + if ( heads ) { + watch_clear_pixel(0, 18); + watch_clear_pixel(2, 18); + } + if ( tails ) { + watch_set_pixel(1, 6); + watch_set_pixel(2, 5); + } + state->animate = false; + state->animation = 0; + movement_request_tick_frequency(1); + } +} + +// DICE FUNCTIONS ///////////////////////////////////////////////////////////// + +/** @brief rolls a dice + */ +uint8_t roll_dice(uint8_t sides) { + uint8_t bits_needed = 0; + uint8_t temp_sides = sides - 1; + uint8_t result = 0; + while (temp_sides > 0) { + bits_needed++; // how many bits do we need to represent this number? + temp_sides >>= 1; // Shift right to check the next bit + } + do { + result = 0; + for (int i = 0; i < bits_needed; i++) { + result <<= 1; // Shift left to make room for the next bit + result |= divine_bit(); // Add the next bit to the result + } + } while ( result > sides -1 ); + return result + 1; // Add 1 to convert the range from 0 to sides-1 to 1 to sides +} + +/** @brief roll multiple dice and print a char array for displaying them + */ +static void _roll_dice_multiple(char* result, uint8_t* dice, uint8_t num_dice) { + // initialize the result array to all spaces + memset(result, ' ', 6); + + // roll the dice and write the result to the result array + for (uint8_t i = 0; i < num_dice-1; i++) { + uint8_t dice_result = dice[i]; + uint8_t tens_digit = dice_result / 10; + uint8_t ones_digit = dice_result % 10; + result[(i * 2)] = tens_digit == 0 ? ' ' : (char)('0' + tens_digit); + result[(i * 2) + 1] = (char)('0' + ones_digit); + } +} + +/** @brief dice animation + */ +static void _dice_animation(toss_up_state_t *state) { + watch_display_string(" ", 4); + for (uint8_t i = 0; i < state->dice_num; i++) { + watch_display_string("0",i*2 + 5); + } + movement_request_tick_frequency(16); + switch ( state->animation ) { + case 0: + watch_clear_pixel(1, 17); + watch_clear_pixel(0, 0); + watch_clear_pixel(1, 6); + break; + case 1: + watch_clear_pixel(2, 20); + watch_clear_pixel(1, 0); + watch_clear_pixel(0, 6); + break; + case 2: + watch_clear_pixel(2, 21); + watch_clear_pixel(2, 0); + watch_clear_pixel(0, 5); + break; + case 3: + watch_clear_pixel(1, 21); + watch_clear_pixel(2, 1); + watch_clear_pixel(1, 4); + break; + case 4: + watch_clear_pixel(0, 21); + watch_clear_pixel(2, 10); + watch_clear_pixel(2, 4); + break; + case 5: + watch_clear_pixel(0, 20); + watch_clear_pixel(0, 1); + watch_clear_pixel(2, 5); + break; + case 6: + watch_clear_pixel(1, 17); + watch_clear_pixel(0, 0); + watch_clear_pixel(1, 6); + break; + case 7: + watch_clear_pixel(2, 20); + watch_clear_pixel(1, 0); + watch_clear_pixel(0, 6); + break; + case 8: + watch_clear_pixel(2, 21); + watch_clear_pixel(2, 0); + watch_clear_pixel(0, 5); + break; + case 9: + watch_clear_pixel(1, 21); + watch_clear_pixel(2, 1); + watch_clear_pixel(1, 4); + break; + case 10: + watch_clear_pixel(0, 21); + watch_clear_pixel(2, 10); + watch_clear_pixel(2, 4); + break; + case 11: + watch_clear_pixel(0, 20); + watch_clear_pixel(0, 1); + watch_clear_pixel(2, 5); + state->animate = false; + state->animation = 0; + movement_request_tick_frequency(1); + } +} \ No newline at end of file diff --git a/movement/watch_faces/complication/toss_up_face.h b/movement/watch_faces/complication/toss_up_face.h new file mode 100644 index 0000000..ca6136a --- /dev/null +++ b/movement/watch_faces/complication/toss_up_face.h @@ -0,0 +1,112 @@ +/* + * MIT License + * + * Copyright (c) 2023 Tobias Raayoni Last / @randogoth + * + * 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 TOSS_UP_FACE_H_ +#define TOSS_UP_FACE_H_ + +#include "movement.h" + +/* + * TOSS UP FACE + * ============ + * + * Playful watch face for games of chance or divination using coins or dice. + * + * LIGHT switches between Coins and Dice mode + * + * COINS + * ===== + * + * ALARM tosses a coin. If it lands on heads it gets sorted to the left side of the + * display, if it lands on tails then sorted to the right side. + * + * LONG PRESSING ALARM adds up to 5 more coins to the toss for more nuance in the decision + * making (e.g. three heads vs two tails could be read as "yes, but with serious doubts"). + * + * LONG PRESSING LIGHT flips through additional style for the coins from the default Ө/O + * to H/T (heads/tails), Y/N (yes/no), E/Ǝ, C/Ↄ + * + * LONG PRESSING ALARM on the "Coins" title page resets to one coin. + * LONG PRESSING LIGHT on the "Coins" title page resets the style to Ө/O + * + * DICE + * ==== + * + * ALARM rolls a six sided dice. + * + * LONG PRESSING ALARM adds up to 2 more dice to the roll. + * + * LONG PRESSING LIGHT flips through other available polyhedral dice types with less or more + * than the default 6 sides. The options are D2, D4, D6, D8, D10, D12, D20, D24, D30, D32, D36, + * D48, and a hypothetical D99. + * + * When more than one dice is used for a roll this changes only the last added dice. (see Note + * below) + * + * LONG PRESSING ALARM on the "Dice" title page resets to one dice. + * LONG PRESSING LIGHT on the "Dice" title page resets the dice to D6. + * + * Please Note: If you need let's say a D8, D12, and D20 for your rolls then the procedure to + * set this up would be as follows: from the default screen where you can roll the one D6 dice + * you would LONG PRESS LIGHT a few times to change the D6 to a D8, then LONG PRESS ALARM to add + * a second dice, LONG PRESS LIGHT again until the second dice changes to D12, then LONG PRESS + * ALARM to add the third dice and LONG PRESS LIGHT again a few times until it becomes a D20. + * + */ + +typedef struct { + // Anything you need to keep track of, put it here! + uint32_t entropy; + uint8_t mode : 4; // 1 coin, 2 coins, 3 coins, 4 coins, dice, iching, geomnc + bool setup; + bool coins[6]; + uint8_t coin_num : 3; + char coin_style[2]; + uint8_t coinface : 3; + uint8_t dice[3]; + uint8_t dice_num : 2; + uint8_t dd : 6; + uint8_t dice_sides[3]; + uint8_t animation; + bool animate; +} toss_up_state_t; + +uint32_t get_true_entropy(void); +uint8_t divine_bit(void); +uint8_t roll_dice(uint8_t sides); +void toss_up_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void toss_up_face_activate(movement_settings_t *settings, void *context); +bool toss_up_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void toss_up_face_resign(movement_settings_t *settings, void *context); + +#define toss_up_face ((const watch_face_t){ \ + toss_up_face_setup, \ + toss_up_face_activate, \ + toss_up_face_loop, \ + toss_up_face_resign, \ + NULL, \ +}) + +#endif // TOSS_UP_FACE_H_ + From c4da02ca2c3197b31d030e75c88df66c8e468b74 Mon Sep 17 00:00:00 2001 From: randogoth Date: Sun, 16 Apr 2023 15:29:42 +0100 Subject: [PATCH 2/4] Randonaut Watch Face (#233) * init * advanced latlon setting * simple functionality done * lat lon high precision fwd bwd * edit toggle * added readme for branch * DD DMS conversion & cleanup * DD to OLC conversion * olc encoding & decoding * OLC implementation * swapped bools for modes, code cleanup * place name editor * updated button logic, fixed display * load and save places in state array * todo list * simplified OLC functions * geohash conversion functions * geohash display & digit functions * todo * finished geohash implementation * code display function, defaults, bugfixes * read/write file/reg logic * long light in DATA to cancel * write to registry * todo * read & write backup register * file read/write * todo * new more concise button logic, optimizations * todo * renamed & cleaned up, fixed button logic * documentation * documentation * LAP mode for all coordinate screens * faster and more precise geohash algorithm * updated description * updated docu * simple place face * bugfixes, updated documentation * init * meh * added public functions for OLC and Geohash * randonauting face * fix * display fix * cleanup * bugfixes * bugfix * added place * fixed TRNG call * fixed declaration conflict * modulo bias filter * simplified things, chance RNG selection * fixed button logic, better menus * cleanup * documentation * docu fixes * original README * updated place_face * fallback to register location * removed pointless freq req * reset config * fixed dependency issue * minor bugfixes * saved a bit :) * fix --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../watch_faces/complication/randonaut_face.c | 411 ++++++++++++++++++ .../watch_faces/complication/randonaut_face.h | 113 +++++ movement/watch_faces/settings/place_face.h | 231 ++++++++++ 5 files changed, 757 insertions(+) create mode 100644 movement/watch_faces/complication/randonaut_face.c create mode 100644 movement/watch_faces/complication/randonaut_face.h create mode 100644 movement/watch_faces/settings/place_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index d0d5398..952f3c7 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -111,6 +111,7 @@ SRCS += \ ../watch_faces/complication/invaders_face.c \ ../watch_faces/clock/world_clock2_face.c \ ../watch_faces/complication/time_left_face.c \ + ../watch_faces/complication/randonaut_face.c \ ../watch_faces/complication/toss_up_face.c \ ../watch_faces/complication/geomancy_face.c \ # New watch faces go above this line. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index a75deae..0e2a45b 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -87,6 +87,7 @@ #include "invaders_face.h" #include "world_clock2_face.h" #include "time_left_face.h" +#include "randonaut_face.h" #include "toss_up_face.h" #include "geomancy_face.h" #include "dual_timer_face.h" diff --git a/movement/watch_faces/complication/randonaut_face.c b/movement/watch_faces/complication/randonaut_face.c new file mode 100644 index 0000000..a4ed6ec --- /dev/null +++ b/movement/watch_faces/complication/randonaut_face.c @@ -0,0 +1,411 @@ +/* + * MIT License + * + * Copyright (c) 2023 Tobias Raayoni Last / @randogoth + * + * 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. + */ + +// Emulator only: need time() to seed the random number generator. +#if __EMSCRIPTEN__ +#include +#else +#include "saml22j18a.h" +#endif + +#include +#include +#include +#include "filesystem.h" +#include "randonaut_face.h" + +#define R 6371 // Earth's radius in km +#define PI 3.14159265358979323846 + +static void _get_location_from_file(randonaut_state_t *state); +static void _save_point_to_file(randonaut_state_t *state); +static void _get_entropy(randonaut_state_t *state); +static void _generate_blindspot(randonaut_state_t *state); +static void _randonaut_face_display(randonaut_state_t *state); +static void _generate_blindspot(randonaut_state_t *state); +static uint32_t _get_pseudo_entropy(uint32_t max); +static uint32_t _get_true_entropy(void); +static void _get_entropy(randonaut_state_t *state); +static uint32_t (*__0x2_)(uint32_t) = &_get_pseudo_entropy; +static void (*_0x22)(uint8_t,uint8_t) = &watch_clear_pixel; +static void (*___0xf322)(uint8_t,uint8_t) = &watch_set_pixel; + +// MOVEMENT WATCH FACE FUNCTIONS ////////////////////////////////////////////// + +void randonaut_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(randonaut_state_t)); + memset(*context_ptr, 0, sizeof(randonaut_state_t)); + // Do any one-time tasks in here; the inside of this conditional happens only at boot. + } + // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. +} + +void randonaut_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + randonaut_state_t *state = (randonaut_state_t *)context; + _get_location_from_file(state); + state->face.mode = 0; + state->radius = 1000; + _get_entropy(state); + state->chance = true; + // Handle any tasks related to your watch face coming on screen. +} + +bool randonaut_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + randonaut_state_t *state = (randonaut_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + // Show your initial UI here. + break; + case EVENT_TICK: + // If needed, update your display here. + break; + case EVENT_LIGHT_BUTTON_DOWN: + break; + case EVENT_LIGHT_BUTTON_UP: + switch ( state->face.mode ) { + case 0: // home + state->face.mode = 2; //point + state->face.location_format = 0; // title + break; + case 1: // generate + state->face.mode = 0; //home + break; + case 2: // point + state->face.mode = 0; //home + break; + case 3: // setup radius + state->face.mode = 4; // toggle to RNG + break; + case 4: // setup RNG + state->face.mode = 3; // toggle to Radius + break; + case 5: // data processing + break; + } + break; + case EVENT_LIGHT_LONG_PRESS: + switch ( state->face.mode ) { + case 3: // setup + case 4: + state->face.mode = 0; //home + break; + default: + state->face.mode = 3; //setup + watch_clear_display(); + } + break; + case EVENT_ALARM_BUTTON_UP: + switch ( state->face.mode ) { + case 0: //home + state->face.mode = 1; // generate + break; + case 2: // point + state->face.location_format = (( state->face.location_format + 1) % (7)); + if ( state->face.location_format == 0 ) + state->face.location_format++; + break; + case 3: //setup radius + state->radius += 500; + if ( state->radius > 10000 ) + state->radius = 1000; + break; + case 4: //setup RNG + state->face.rng = (state->face.rng + 1) % 3; + switch ( state->face.rng ) { + case 0: + state->chance = true; + break; + case 1: + state->chance = false; + state->quantum = true; + break; + case 2: + state->chance = false; + state->quantum = false; + break; + } + break; + case 5: // data processing + _save_point_to_file(state); + break; + default: + break; + } + break; + case EVENT_ALARM_LONG_PRESS: + if ( state->face.mode == 5 ) + state->face.mode = 0; // home + else + state->face.mode = 5; // data processing + 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: + // 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: + // Movement's default loop handler will step in for any cases you don't handle above: + // * EVENT_LIGHT_BUTTON_DOWN lights the LED + // * EVENT_MODE_BUTTON_UP moves to the next watch face in the list + // * EVENT_MODE_LONG_PRESS returns to the first watch face (or skips to the secondary watch face, if configured) + // You can override any of these behaviors by adding a case for these events to this switch statement. + return movement_default_loop_handler(event, settings); + } + + _randonaut_face_display(state); + + // return true if the watch can enter standby mode. Generally speaking, you should always return true. + // Exceptions: + // * If you are displaying a color using the low-level watch_set_led_color function, you should return false. + // * If you are sounding the buzzer using the low-level watch_set_buzzer_on function, you should return false. + // Note that if you are driving the LED or buzzer using Movement functions like movement_illuminate_led or + // movement_play_alarm, you can still return true. This guidance only applies to the low-level watch_ functions. + return true; +} + +void randonaut_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + + // handle any cleanup before your watch face goes off-screen. +} + +// PRIVATE STATIC FUNCTIONS /////////////////////////////////////////////////// + +/** @brief display handler + */ +static void _randonaut_face_display(randonaut_state_t *state) { + char buf[11]; + watch_clear_colon(); + switch ( state->face.mode ) { + case 0: //home + sprintf(buf, "RA Rando"); + break; + case 1: //generate + if ( state->quantum ) + // All Hail Steve /;[;[/.;]/[.;[/;/;/;/;.;.];.]]--=/ + for ( uint8_t c = 100; c > 0; c--) {//////////////// + ___0xf322(__0x2_(/*0xD68 _0x22*//*__0x22*/////// + /*_0x22*/0x2),__0x2_(/* _0x22*//*_0x22*//////// + 0x33-0x1C));___0xf322(__0x2_(/*___0x2222_22___*/ + /*0x2*/0x2),__0x2_(3432/*_0x22*//*_0x2222*////// + -3409));___0xf322(__0x2_(/*0x2*//*____0x222222*/ + 002),__0x2_(0xE +9));___0xf322(/*_0x2222222222*/ + __0x2_(0x2),__0x2_(23));___0xf322(/*____0x2222*/ + /*0x2*/__0x2_(002),__0x2_(12+7+11));/*___00x22*/ + if(/*_0x22*/c<70){_0x22(__0x2_(2),/*____0x2222*/ + __0x2_(12+7+11));}if(c<60){_0x22(/*_______0x22*/ + /*_0x22*/__0x2_(002),__0x2_(0xD68-/*_0x2222222*/ + 0xD4A));}if(c<50){_0x22(__0x2_(0x2),/*____0x22*/ + __0x2_(14+9));}delay_ms(__0x2_(c)+20);if/*_0x2*/ + (c<30){watch_display_string(" ",__0x2_(/*_2**2*/ + 10));}_0x22(__0x2_(02),__0x2_(3432-3409)/*0x22*/ + );_0x22(__0x2_(002),__0x2_(51-28));/*_____0x22*/ + /**/ _0x22(__0x2_(0x2),__0x2_(23));if(c<20)//// + /*_*/{_0x22(__0x2_(02),__0x2_(51-28));/*__0x22*/ + /*_0x22*/_0x22(__0x2_(2),__0x2_(14+9));/*_0x22*/ + /*_0x22*/_0x22(__0x2_(0x2),__0x2_(0xD68-0xD4A)); + /*_0x22*/_0x22(__0x2_(0x2),__0x2_(3432-3409));// + /*_0x22*/_0x22(__0x2_(002),__0x2_(12+7+11));//// + /*_0x22**_0x22*/_0x22(__0x2_(2),__0x2_(51-28));} + } + else + for ( uint8_t c = 30; c > 0; c--) { + watch_display_string("1", _get_pseudo_entropy(10)); + watch_display_string("0", _get_pseudo_entropy(10)); + watch_display_string("11", _get_pseudo_entropy(10)); + watch_display_string("00", _get_pseudo_entropy(10)); + delay_ms(50); + watch_display_string(" ", _get_pseudo_entropy(10)); + watch_display_string(" ", _get_pseudo_entropy(10)); + watch_display_string(" ", _get_pseudo_entropy(10)); + watch_display_string(" ", _get_pseudo_entropy(10)); + } + _generate_blindspot(state); + watch_clear_display(); + state->face.mode = 2; // point + state->face.location_format = 1; // distance + watch_display_string("RA Found", 0); + delay_ms(500); + sprintf(buf, "RA Found"); + break; + case 2: //point + switch ( state->face.location_format ) { + case 0: + sprintf(buf, "RA Point"); + break; + case 1: // distance to point + watch_clear_display(); + sprintf(buf, "DI m %d", state->point.distance ); + break; + case 2: // bearing relative to point + watch_clear_display(); + sprintf(buf, "BE # %d", state->point.bearing ); + break; + case 3: // latitude DD._____ + sprintf(state->scratchpad, "%07d", abs(state->point.latitude)); + sprintf(buf, "LA #%c %c%c ", state->point.latitude < 0 ? '-' : '+', state->scratchpad[0], state->scratchpad[1]); + break; + case 4: // latitude __.DDDDD + sprintf(buf, "LA , %c%c%c%c%c", state->scratchpad[2], state->scratchpad[3],state->scratchpad[4], state->scratchpad[5],state->scratchpad[6]); + break; + case 5: // longitude DD._____ + sprintf(state->scratchpad, "%08d", abs(state->point.longitude)); + sprintf(buf, "LO #%c%c%c%c ", state->point.longitude < 0 ? '-' : '+',state->scratchpad[0], state->scratchpad[1], state->scratchpad[2]); + break; + case 6: // longitude __.DDDDD + sprintf(buf, "LO , %c%c%c%c%c", state->scratchpad[3], state->scratchpad[4],state->scratchpad[5], state->scratchpad[6],state->scratchpad[7]); + break; + } + break; + case 3: // setup radius + watch_set_colon(); + if ( state->radius < 10000 ) + sprintf(buf, "RA m %d ", state->radius); + else + sprintf(buf, "RA m%d ", state->radius); + break; + case 4: // setup RNG + sprintf(buf, "RN G %s ", state->chance ? "Chnce" : (state->quantum ? "True" : "Psudo"), state->radius); + break; + case 5: // data processing + sprintf(buf, "WR File "); + } + watch_display_string(buf, 0); +} + +/** @brief Official Randonautica Blindspot Algorithm + */ +static void _generate_blindspot(randonaut_state_t *state) { + + _get_entropy(state); + + double lat = (double)state->location.latitude / 100000; + double lon = (double)state->location.longitude / 100000; + uint16_t radius = state->radius; + + const double random_distance = radius * sqrt( (double)state->entropy / INT32_MAX ) / 1000.0; + const double random_bearing = 2.0 * PI * (double)state->entropy / INT32_MAX; + + const double phi = lat * PI / 180; + const double lambda = lon * PI / 180; + const double alpha = random_distance / R; + + lat = asin( sin(phi) * cos(alpha) + cos(phi) * sin(alpha) * cos(random_bearing) ); + lon = lambda + atan2( sin(random_bearing) * sin(alpha) * cos(phi), cos(alpha) - sin(phi) * sin( lat )); + + state->point.latitude = (int)round(lat * (180 / PI) * 100000); + state->point.longitude = (int)round(lon * (180 / PI) * 100000); + state->point.distance = random_distance * 1000; + state->point.bearing = (uint16_t)round(random_bearing * (180 / PI) < 0 ? random_bearing * (180 / PI) + 360 : random_bearing * (180 / PI)); +} + + +/** @brief pseudo random number generator + */ +static uint32_t _get_pseudo_entropy(uint32_t max) { + #if __EMSCRIPTEN__ + return rand() % max; + #else + return arc4random_uniform(max); + #endif +} + +/** @brief true random number generator + */ +static uint32_t _get_true_entropy(void) { + #if __EMSCRIPTEN__ + return rand() % INT32_MAX; + #else + hri_mclk_set_APBCMASK_TRNG_bit(MCLK); + hri_trng_set_CTRLA_ENABLE_bit(TRNG); + + while (!hri_trng_get_INTFLAG_reg(TRNG, TRNG_INTFLAG_DATARDY)); // Wait for TRNG data to be ready + + hri_trng_clear_CTRLA_ENABLE_bit(TRNG); + hri_mclk_clear_APBCMASK_TRNG_bit(MCLK); + return hri_trng_read_DATA_reg(TRNG); // Read a single 32-bit word from TRNG and return it + #endif +} + +/** @brief get location from place.loc + */ +static void _get_location_from_file(randonaut_state_t *state) { + movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1); + coordinate_t place; + if (filesystem_file_exists("place.loc")) { + if (filesystem_read_file("place.loc", (char*)&place, sizeof(place))) + state->location = place; + } else { + watch_set_indicator(WATCH_INDICATOR_BELL); + state->location.latitude = movement_location.bit.latitude * 1000; + state->location.longitude = movement_location.bit.longitude * 1000; + } +} + +/** @brief save generated point to place.loc + */ +static void _save_point_to_file(randonaut_state_t *state) { + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + coordinate_t place; + place.latitude = state->point.latitude; + place.longitude = state->point.longitude; + if (filesystem_write_file("place.loc", (char*)&place, sizeof(place))) { + delay_ms(100); + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + } else { + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + watch_set_indicator(WATCH_INDICATOR_BELL); + delay_ms(500); + watch_clear_indicator(WATCH_INDICATOR_BELL); + + } +} + +/** @brief get pseudo/quantum entropy and filter modulo bias + */ +static void _get_entropy(randonaut_state_t *state) { + if ( state->chance ) { + state->quantum = (bool)(state->entropy % 2); + } + do { + if ( !state->quantum ) { + state->entropy = _get_pseudo_entropy(INT32_MAX); + } else { + state->entropy = _get_true_entropy(); + } + } while (state->entropy >= INT32_MAX || state->entropy <= 0); + state->entropy %= INT32_MAX; +} \ No newline at end of file diff --git a/movement/watch_faces/complication/randonaut_face.h b/movement/watch_faces/complication/randonaut_face.h new file mode 100644 index 0000000..fabde79 --- /dev/null +++ b/movement/watch_faces/complication/randonaut_face.h @@ -0,0 +1,113 @@ +/* + * MIT License + * + * Copyright (c) 2023 Tobias Raayoni Last / @randogoth + * + * 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 RANDONAUT_FACE_H_ +#define RANDONAUT_FACE_H_ + +#include "movement.h" +#include "place_face.h" + +/* + * RANDONAUT FACE + * ============== + * + * Randonauting is a way to turn the world around you into an adventure and get the user outside + * of their day-to-day routine by using a random number generator to derive a coordinate to journey + * to. In Randonauts lore so-called "Blind Spots" are places you cannot reach methodologically. They + * may exist in your own backyard for your whole life and you will never even notice them, because + * you simply have no reason to go to that exact place or look in its direction. Since the very + * limitations of our behavioral algorithms are the reason for the existence of blindspots, they + * can only be found using a randomizer. + * + * This watch face generates a random location based on the watch's location and a set radius using + * the official Randonautica Blind Spot algorithm. + * + * The ALARM button starts the random location generation and then automatically displays the found + * Blind Spot. + * + * By pressing ALARM again the user can flip through different pieces of information about the Blind + * Spot: Distance (DI), Bearing Degree (BE), Latitude degrees and decimal digits (LA), Longitude + * degrees and decimal digits (LO). + * + * Pressing LIGHT switches between generating a new blind spot ("Rando") and displaying the info of + * the last generated one ("Point"). + * + * LONG PRESSING LIGHT toggles setup mode. Here pressing LIGHT switches between setting the desired + * radius (RA) and setting the random number generator (RNG) for generating the blind spot. + * + * ALARM changes the values respectively: + * + * - The radius can be set in 500 meter steps between 1000 and 10,000 meters + * + * - The RNG can be set to "true" which utilizes the SAML22J's internal True Random Number Generator + * - Setting it to "psudo" will use the pseudorandom number generation algorithm arc4random + * - Setting it to "chance" will randomly chose either of the RNGs for each generation (default) + * + * LONG PRESSING ALARM toggles DATA mode in which the currently generated Blind Spot coordinate can + * be written to the file on the watch (press ALARM) and set as active high precision + * location used by other watch faces. It does not overwrite the low precision location information + * in the watch register commonly used for astronomical watch faces. + * + */ + +typedef struct { + uint8_t mode :3; + uint8_t location_format :3; + uint8_t rng: 2; +} randonaut_face_mode_t; + +typedef struct { + int32_t latitude : 26; + int32_t longitude : 26; + uint16_t distance : 14; + uint16_t bearing : 9; +} randonaut_coordinate_t; + +typedef struct { + // Anything you need to keep track of, put it here! + coordinate_t location; + randonaut_coordinate_t point; + uint16_t radius : 14; + uint32_t entropy; + bool quantum; + bool chance; + randonaut_face_mode_t face; + char scratchpad[10]; +} randonaut_state_t; + +void randonaut_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void randonaut_face_activate(movement_settings_t *settings, void *context); +bool randonaut_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void randonaut_face_resign(movement_settings_t *settings, void *context); + +#define randonaut_face ((const watch_face_t){ \ + randonaut_face_setup, \ + randonaut_face_activate, \ + randonaut_face_loop, \ + randonaut_face_resign, \ + NULL, \ +}) + +#endif // RANDONAUT_FACE_H_ + diff --git a/movement/watch_faces/settings/place_face.h b/movement/watch_faces/settings/place_face.h new file mode 100644 index 0000000..a98c264 --- /dev/null +++ b/movement/watch_faces/settings/place_face.h @@ -0,0 +1,231 @@ +/* + * MIT License + * + * Copyright (c) 2023 Tobias Raayoni Last / @randogoth + * + * 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 place_FACE_H_ +#define place_FACE_H_ + +#include "movement.h" + +/* + * PLACE FACE + * ========== + * + * Based on and expanded from the Sunrise/Sunset face. Outsourced the location setting functionality to + * its own face. Also serves as a converter between different coordinate notation formats. + * + * With the LIGHT button each place coordinate can be shown and edited in 4 different display modes: + * + * 1) Decimal Latitude and Longitude (WGS84) up to 5 decimal points + * 2) Latitude and Longitude (WGS84) in traditional DD°MM'SS" notation + * 3) Ten digit Open Location Code (aka. PlusCode) format + * 4) Ten digit Geohash format + * + * Using the ALARM button the user can flip through 2 pages of coordinate info to see the first and + * second sets of digits. + * + * (please also refer to the notes on precision below) + * + * Editing Mode + * ============ + * + * A LONG PRESS of the LIGHT button toggles editing mode for each of the selected notations. + * + * In this mode LIGHT moves the cursor and ALARM changes the letter cycling through the available + * alphabet or numbers. + * + * When OLC or Geohash display are edited, Digit Info mode is activated. It serves as a workaround + * for the limitation of how ambiguously alphanumeric characters are displayed on the main seven segment + * digits of the watch face ( S or 5, I or 1, U or W?). + * + * The selected letter is also shown in the much easier to read alphanumeric 8 segment weekday digit above. + * In addition the '24H' indicator is active when the selected digit represents a number and the 'PM' + * indicator for a letter. + * + * A LONG PRESS of LIGHT saves the changes. + * + * Coordinates are read or stored to both the traditional internal location register and a file on + * the LFS file system ("place.loc"). By default the Watch Face loads the coordinates from file + * when activated. If no file is present, the coordinates are loaded from the register. + * (please also see the notes on precision below) + * + * Auxiliary Mode: Digit Info + * ========================== + * + * A LONG PRESS of the ALARM button toggles Digit Info mode when OLC or Geohash display is active. + * (LAP indicator is on) It is a means of being able to see the detailed Digit Info as described above + * but without the risk of accidentally editing any of digits. + * + * Both ALARM and LIGHT buttons can be used to flip through the letters. + * + * Notes on Coordinate Precision + * ============================= + * + * The common WGS84 Latitude and Longitude degrees naturally do not represent meters in distance + * on the ground. 1° Longitude on the equatorial line equals a width of 111.32 kilometers, but + * at 40° latitude further North or South it is approximately 85 kilometers wide. The closer to + * the poles the narrower (and more precise) the latitude degrees get. + * + * The Sensor Watch's traditional 16bit location register only stores latitudes and longitudes + * with two decimal points. That equals a longitudal precision of 36 arc seconds, or ~1111 meters + * at the equator - precise enough for astronomical calculations, but not if you want to store the + * location of let's say a building. + * + * Hence we propose the file that serves the same purpose, but with a precision of + * five decimal digits. That equals 0.04 arc seconds or 1.11 meters at the equator. + * + * Please also note that the different notations of this watch face also have varying magnitudes + * of precision: + * + * | Format | Notation | Precision at Equator | Precision at 67° N/S | + * | ------------------ | ---------------------- | -------------------- | -------------------- | + * | 2d. Decimal LatLon | 29.98, 31.13 | 1111.320 m | 435.125 m | + * | 5d. Decimal LatLon | 29.97916, 31.13417 | 1.111 m | 0.435 m | + * | DMS LatLon | N 29°58′45″, E 31°8′3″ | 30.833 m | 12.083 m | + * | Open Location Code | 7GXHX4HM+MM | 13.875 m | 13.875 m | + * | Geohash | stq4s3x1qu | 1.189 m | 0.596 m | + * + * Since all notations are internally converted into degrees with 5 decimal points, expect some + * rounding errors when editing or loading the coordinates in other notation formats. + * + */ + +static const char olc_alphabet[20] = "23456789CFGHJMPQRUWX"; +static const char geohash_alphabet[32] = "0123456789bCdEfGhjkmnpqrstuVwxyz"; + +typedef struct { + uint8_t sign: 1; // 0-1 + uint8_t hundreds: 1; // 0-1 + uint8_t tens: 4; // 0-9 + uint8_t ones: 4; // 0-9 + uint8_t d01: 4; // 0-9 + uint8_t d02: 4; // 0-9 + uint8_t d03: 4; // 0-9 + uint8_t d04: 4; // 0-9 + uint8_t d05: 4; // 0-9 +} place_format_decimal_latlon_t; + +typedef struct { + uint8_t sign: 1; // 0-1 + uint8_t hundreds: 1; // 0-1 + uint8_t tens: 4; // 0-9 + uint8_t ones: 4; // 0-9 + uint8_t mins_tens: 3; // 0-5 + uint8_t mins_ones: 4; // 0-9 + uint8_t secs_tens: 3; // 0-5 + uint8_t secs_ones: 4; // 0-9 +} place_format_dms_latlon_t; + +typedef struct { + uint8_t lat1: 5; // 2-X + uint8_t lon1: 5; // 2-X + uint8_t lat2: 5; // 2-X + uint8_t lon2: 5; // 2-X + uint8_t lat3: 5; // 2-X + uint8_t lon3: 5; // 2-X + uint8_t lat4: 5; // 2-X + uint8_t lon4: 5; // 2-X + uint8_t lat5: 5; // 2-X + uint8_t lon5: 5; // 2-X +} place_format_olc_t; + +typedef struct { + int32_t latitude : 25; + int32_t longitude : 26; +} coordinate_t; + +typedef struct { + place_format_decimal_latlon_t latitude; + place_format_decimal_latlon_t longitude; +} place_coordinate_t; + +typedef struct { + uint8_t d01: 6; // 0-z + uint8_t d02: 6; // 0-z + uint8_t d03: 6; // 0-z + uint8_t d04: 6; // 0-z + uint8_t d05: 6; // 0-z + uint8_t d06: 6; // 0-z + uint8_t d07: 6; // 0-z + uint8_t d08: 6; // 0-z + uint8_t d09: 6; // 0-z + uint8_t d10: 6; // 0-z +} place_format_geohash_t; + +typedef struct { + double max; + double min; +} place_format_geohash_interval; + +typedef struct { + uint8_t min_digit : 1; + uint8_t max_digit : 3; +} place_mode_schema_page_t; + +typedef struct { + uint8_t max_page : 3; + place_mode_schema_page_t page[4]; +} place_mode_schema_mode_t; + +enum place_modes_e { + MODE_DECIMAL = 0, + MODE_DMS, + MODE_OLC, + MODE_GEOHASH +}; + +typedef struct { + enum place_modes_e mode; + uint8_t page : 3; + int8_t active_digit: 4; + bool edit; + bool digit_info; + place_format_decimal_latlon_t working_latitude; + place_format_decimal_latlon_t working_longitude; + place_format_dms_latlon_t working_dms_latitude; + place_format_dms_latlon_t working_dms_longitude; + place_format_olc_t working_pluscode; + place_format_geohash_t working_geohash; + place_mode_schema_mode_t modes[4]; +} place_state_t; + +// PUBLIC WATCH FACE FUNCTIONS //////////////////////////////////////////////// + +void place_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void place_face_activate(movement_settings_t *settings, void *context); +bool place_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void place_face_resign(movement_settings_t *settings, void *context); + +void place_latlon_to_olc(char *pluscode, double latitude, double longitude); +void place_latlon_to_geohash(char *geohash, double latitude, double longitude); + +#define place_face ((const watch_face_t){ \ + place_face_setup, \ + place_face_activate, \ + place_face_loop, \ + place_face_resign, \ + NULL, \ +}) + +#endif // place_FACE_H_ + From 00541bd9c51101d58dfb8e88ae59b968a4094d4e Mon Sep 17 00:00:00 2001 From: joeycastillo Date: Sun, 16 Apr 2023 11:06:11 -0400 Subject: [PATCH 3/4] fix most warnings --- movement/watch_faces/complication/geomancy_face.c | 13 ++++++++++--- movement/watch_faces/complication/randonaut_face.c | 2 +- movement/watch_faces/complication/toss_up_face.c | 3 +++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/movement/watch_faces/complication/geomancy_face.c b/movement/watch_faces/complication/geomancy_face.c index 689d4e1..c741b2d 100644 --- a/movement/watch_faces/complication/geomancy_face.c +++ b/movement/watch_faces/complication/geomancy_face.c @@ -72,6 +72,7 @@ static void _throw_animation(geomancy_state_t *state); // WATCH FACE FUNCTIONS /////////////////////////////////////////////////////// void geomancy_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) watch_face_index; (void) settings; if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(geomancy_state_t)); @@ -112,12 +113,14 @@ bool geomancy_face_loop(movement_event_t event, movement_settings_t *settings, v switch ( state->mode ) { case 0: state->mode++; + // fall through case 1: state->animate = true; state->i_ching_hexagram = _iching_form_hexagram(); break; case 2: state->mode++; + // fall through case 3: state->animate = true; state->geomantic_figure = _geomancy_pick_figure().bits; @@ -147,6 +150,7 @@ void geomancy_face_resign(movement_settings_t *settings, void *context) { // STATIC FUNCTIONS /////////////////////////////////////////////////////////// /** @brief display handler */ +static void geomancy_face_display(geomancy_state_t *state); static void geomancy_face_display(geomancy_state_t *state) { char token[7] = {0}; nibble_t figure = *((nibble_t*) &state->geomantic_figure); @@ -287,7 +291,7 @@ static void _throw_animation(geomancy_state_t *state) { /** @brief form a trigram from three random bit picks */ -static tribble_t _iching_pick_trigram() { +static tribble_t _iching_pick_trigram(void) { uint8_t index = (divine_bit() << 2) | (divine_bit() << 1) | divine_bit(); tribble_t trigram = {(bagua >> (3 * index)) & 0b111}; return trigram; @@ -295,7 +299,8 @@ static tribble_t _iching_pick_trigram() { /** @brief form a hexagram from two trigrams */ -static uint8_t _iching_form_hexagram() { +static uint8_t _iching_form_hexagram(void); +static uint8_t _iching_form_hexagram(void) { tribble_t inner = _iching_pick_trigram(); tribble_t outer = _iching_pick_trigram(); uint8_t hexagram = (inner.bits << 3) | outer.bits; @@ -305,6 +310,7 @@ static uint8_t _iching_form_hexagram() { /** @brief display hexagram * @details | for unbroken lines and Ξ for broken lines, left of display is bottom */ +static void _display_hexagram(uint8_t hexagram, char* str); static void _display_hexagram(uint8_t hexagram, char* str) { str[6] = '\0'; // Null-terminate the string for (uint8_t i = 0; i < 6; i++) { @@ -334,7 +340,8 @@ static void _fix_broken_line(uint8_t hexagram) { /** @brief choose a geomantic figure from four random bits * @details 0 represents · and 1 represents : counting from the bottom */ -static nibble_t _geomancy_pick_figure() { +static nibble_t _geomancy_pick_figure(void); +static nibble_t _geomancy_pick_figure(void) { uint8_t index = (divine_bit() << 3) | (divine_bit() << 2) | (divine_bit() << 1) | divine_bit(); nibble_t figure = {(geomantic >> (4 * (15 - index))) & 0xF}; return figure; diff --git a/movement/watch_faces/complication/randonaut_face.c b/movement/watch_faces/complication/randonaut_face.c index a4ed6ec..3ff5162 100644 --- a/movement/watch_faces/complication/randonaut_face.c +++ b/movement/watch_faces/complication/randonaut_face.c @@ -207,7 +207,7 @@ void randonaut_face_resign(movement_settings_t *settings, void *context) { /** @brief display handler */ static void _randonaut_face_display(randonaut_state_t *state) { - char buf[11]; + char buf[12]; watch_clear_colon(); switch ( state->face.mode ) { case 0: //home diff --git a/movement/watch_faces/complication/toss_up_face.c b/movement/watch_faces/complication/toss_up_face.c index 1923302..f244f3c 100644 --- a/movement/watch_faces/complication/toss_up_face.c +++ b/movement/watch_faces/complication/toss_up_face.c @@ -45,6 +45,7 @@ static void _coin_animation(toss_up_state_t *state); // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////// void toss_up_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) watch_face_index; (void) settings; if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(toss_up_state_t)); @@ -95,6 +96,7 @@ bool toss_up_face_loop(movement_event_t event, movement_settings_t *settings, vo switch (state->mode) { case 0: state->mode++; + // fall through case 1: state->animate = true; for (i = 0; i < state->coin_num; i++) { @@ -103,6 +105,7 @@ bool toss_up_face_loop(movement_event_t event, movement_settings_t *settings, vo break; case 2: state->mode++; + // fall through case 3: state->animate = true; for (i = 0; i < state->dice_num; i++) { From 432de347091e0f79c4e82e850a0192f27fea7e55 Mon Sep 17 00:00:00 2001 From: randogoth Date: Sun, 16 Apr 2023 21:54:26 +0300 Subject: [PATCH 4/4] deobfuscated code, fixed warnings --- .../watch_faces/complication/randonaut_face.c | 59 ++++++++++--------- .../watch_faces/complication/toss_up_face.c | 4 +- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/movement/watch_faces/complication/randonaut_face.c b/movement/watch_faces/complication/randonaut_face.c index 3ff5162..1a3eb21 100644 --- a/movement/watch_faces/complication/randonaut_face.c +++ b/movement/watch_faces/complication/randonaut_face.c @@ -47,9 +47,6 @@ static void _generate_blindspot(randonaut_state_t *state); static uint32_t _get_pseudo_entropy(uint32_t max); static uint32_t _get_true_entropy(void); static void _get_entropy(randonaut_state_t *state); -static uint32_t (*__0x2_)(uint32_t) = &_get_pseudo_entropy; -static void (*_0x22)(uint8_t,uint8_t) = &watch_clear_pixel; -static void (*___0xf322)(uint8_t,uint8_t) = &watch_set_pixel; // MOVEMENT WATCH FACE FUNCTIONS ////////////////////////////////////////////// @@ -216,30 +213,36 @@ static void _randonaut_face_display(randonaut_state_t *state) { case 1: //generate if ( state->quantum ) // All Hail Steve /;[;[/.;]/[.;[/;/;/;/;.;.];.]]--=/ - for ( uint8_t c = 100; c > 0; c--) {//////////////// - ___0xf322(__0x2_(/*0xD68 _0x22*//*__0x22*/////// - /*_0x22*/0x2),__0x2_(/* _0x22*//*_0x22*//////// - 0x33-0x1C));___0xf322(__0x2_(/*___0x2222_22___*/ - /*0x2*/0x2),__0x2_(3432/*_0x22*//*_0x2222*////// - -3409));___0xf322(__0x2_(/*0x2*//*____0x222222*/ - 002),__0x2_(0xE +9));___0xf322(/*_0x2222222222*/ - __0x2_(0x2),__0x2_(23));___0xf322(/*____0x2222*/ - /*0x2*/__0x2_(002),__0x2_(12+7+11));/*___00x22*/ - if(/*_0x22*/c<70){_0x22(__0x2_(2),/*____0x2222*/ - __0x2_(12+7+11));}if(c<60){_0x22(/*_______0x22*/ - /*_0x22*/__0x2_(002),__0x2_(0xD68-/*_0x2222222*/ - 0xD4A));}if(c<50){_0x22(__0x2_(0x2),/*____0x22*/ - __0x2_(14+9));}delay_ms(__0x2_(c)+20);if/*_0x2*/ - (c<30){watch_display_string(" ",__0x2_(/*_2**2*/ - 10));}_0x22(__0x2_(02),__0x2_(3432-3409)/*0x22*/ - );_0x22(__0x2_(002),__0x2_(51-28));/*_____0x22*/ - /**/ _0x22(__0x2_(0x2),__0x2_(23));if(c<20)//// - /*_*/{_0x22(__0x2_(02),__0x2_(51-28));/*__0x22*/ - /*_0x22*/_0x22(__0x2_(2),__0x2_(14+9));/*_0x22*/ - /*_0x22*/_0x22(__0x2_(0x2),__0x2_(0xD68-0xD4A)); - /*_0x22*/_0x22(__0x2_(0x2),__0x2_(3432-3409));// - /*_0x22*/_0x22(__0x2_(002),__0x2_(12+7+11));//// - /*_0x22**_0x22*/_0x22(__0x2_(2),__0x2_(51-28));} + for ( uint8_t c = 100; c > 0; c--) { + watch_set_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(0x33-0x1C)); + watch_set_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(3432-3409)); + watch_set_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(0xE +9)); + watch_set_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(23)); + watch_set_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(12+7+11)); + if( c < 70 ) { + watch_clear_pixel(_get_pseudo_entropy(2),_get_pseudo_entropy(12+7+11)); + } + if ( c < 60 ) { + watch_clear_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(0xD68-0xD4A)); + } + if ( c < 50 ) { + watch_clear_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(14+9)); + } + delay_ms(_get_pseudo_entropy(c)+20); + if ( c < 30 ) { + watch_display_string(" ",_get_pseudo_entropy(10)); + } + watch_clear_pixel(_get_pseudo_entropy(02),_get_pseudo_entropy(3432-3409)); + watch_clear_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(51-28)); + watch_clear_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(23)); + if ( c < 20 ) { + watch_clear_pixel(_get_pseudo_entropy(02),_get_pseudo_entropy(51-28)); + watch_clear_pixel(_get_pseudo_entropy(2),_get_pseudo_entropy(14+9)); + watch_clear_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(0xD68-0xD4A)); + watch_clear_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(3432-3409)); + watch_clear_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(12+7+11)); + watch_clear_pixel(_get_pseudo_entropy(2),_get_pseudo_entropy(51-28)); + } } else for ( uint8_t c = 30; c > 0; c--) { @@ -298,7 +301,7 @@ static void _randonaut_face_display(randonaut_state_t *state) { sprintf(buf, "RA m%d ", state->radius); break; case 4: // setup RNG - sprintf(buf, "RN G %s ", state->chance ? "Chnce" : (state->quantum ? "True" : "Psudo"), state->radius); + sprintf(buf, "RN G %s ", state->chance ? "Chnce" : (state->quantum ? "True" : "Psudo")); break; case 5: // data processing sprintf(buf, "WR File "); diff --git a/movement/watch_faces/complication/toss_up_face.c b/movement/watch_faces/complication/toss_up_face.c index f244f3c..08dd005 100644 --- a/movement/watch_faces/complication/toss_up_face.c +++ b/movement/watch_faces/complication/toss_up_face.c @@ -303,9 +303,9 @@ static void _coin_animation(toss_up_state_t *state) { bool tails = false; for (uint8_t i = 0; i < state->coin_num; i++) { if (state->coins[i] == true) { - heads++; + heads = true; } else { - tails++; + tails = true; } } movement_request_tick_frequency(32);