]> git.earman.xyz Git - sensor-watch.git/commitdiff
Randonaut Watch Face (#233)
authorrandogoth <kflux@posteo.de>
Sun, 16 Apr 2023 14:29:42 +0000 (15:29 +0100)
committerjoeycastillo <joeycastillo@utexas.edu>
Sun, 16 Apr 2023 15:00:44 +0000 (11:00 -0400)
* 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
movement/movement_faces.h
movement/watch_faces/complication/randonaut_face.c [new file with mode: 0644]
movement/watch_faces/complication/randonaut_face.h [new file with mode: 0644]
movement/watch_faces/settings/place_face.h [new file with mode: 0644]

index d0d53980f2cd0d9ec9b066c5e77f49734fa74027..952f3c709ab99bd567b98d7ea2f522a542668ffa 100644 (file)
@@ -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.
index a75deaecf09c86d30a59e92d8edf0690b53dcaa3..0e2a45b159bff46fd5897d2022e3f61a1dd9a948 100644 (file)
@@ -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 (file)
index 0000000..a4ed6ec
--- /dev/null
@@ -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 <time.h>
+#else
+#include "saml22j18a.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#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 (file)
index 0000000..fabde79
--- /dev/null
@@ -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 <place.loc> 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 (file)
index 0000000..a98c264
--- /dev/null
@@ -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 <place.loc> 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_
+