/* * 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); // 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[12]; 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--) { 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--) { 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((int32_t)(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((int32_t)(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")); 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 watch_disable_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; }