]> git.earman.xyz Git - sensor-watch.git/commitdiff
world clock: support longer cities on custom LCD
authorJoey Castillo <joeycastillo@utexas.edu>
Sat, 15 Mar 2025 19:20:22 +0000 (15:20 -0400)
committerJoey Castillo <joeycastillo@utexas.edu>
Sat, 15 Mar 2025 19:20:22 +0000 (15:20 -0400)
movement.c
movement.h
watch-faces/clock/world_clock_face.c
watch-faces/clock/world_clock_face.h
watch-library/shared/watch/watch_common_display.c

index 27bdf21c6984f27e8768b8f82c7d47d1abdc45f5..7c913577c55bb0c33333b85a38f41a7f3fde88ec 100644 (file)
@@ -66,9 +66,6 @@ movement_event_t event;
 int8_t _movement_dst_offset_cache[NUM_ZONE_NAMES] = {0};
 #define TIMEZONE_DOES_NOT_OBSERVE (-127)
 
-const char movement_valid_position_0_chars[] = " AaBbCcDdEeFGgHhIiJKLMNnOoPQrSTtUuWXYZ-='+\\/0123456789";
-const char movement_valid_position_1_chars[] = " ABCDEFHlJLNORTtUX-='01378";
-
 void cb_mode_btn_interrupt(void);
 void cb_light_btn_interrupt(void);
 void cb_alarm_btn_interrupt(void);
index 8031daebc18a7540786e058332a8ad21e8654a88..c2343e2907dc3b3b024ae42315facdf8fbaf0466 100644 (file)
@@ -155,8 +155,6 @@ typedef struct {
 } movement_event_t;
 
 extern const int16_t movement_timezone_offsets[];
-extern const char movement_valid_position_0_chars[];
-extern const char movement_valid_position_1_chars[];
 
 /** @brief Perform setup for your watch face.
   * @details It's tempting to say this is 'one-time' setup, but technically this function is called more than
index 3bb6cbe28b2333378f9261e535a2b4f3c534f7cc..356f9437f65f81e4f4db496b3a5e8ccc44d83ba9 100644 (file)
 #include "watch.h"
 #include "watch_utility.h"
 #include "watch_common_display.h"
+#include "filesystem.h"
 #include "zones.h"
 
-void _update_timezone_offset(world_clock_state_t *state);
-void _update_timezone_offset(world_clock_state_t *state) {
+static int world_clock_instances;
+
+static void persist_world_clock_settings(world_clock_state_t *state) {
+    world_clock_settings_t maybe_settings;
+    char filename[13];
+
+    maybe_settings.reg = 0xFFFFFFFF;
+    sprintf(filename, "wclk_%03d.u32", state->clock_index);
+
+    filesystem_read_file(filename, (char *) &maybe_settings.reg, sizeof(world_clock_settings_t));
+    if (state->settings.reg != maybe_settings.reg) {
+        filesystem_write_file(filename, (char *) &state->settings.reg, sizeof(world_clock_settings_t));
+    }
+}
+
+static void advance_character_at_position(char *character, uint8_t position) {
+    bool is_custom_lcd = watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM;
+    if (is_custom_lcd || position == 0) {
+        // On custom LCD and classic's position 0 we support all characters.
+        // All we need to do is jump around the ASCII table to the useful ones.
+        switch (*character) {
+            case ' ':
+                *character = 'A';
+                break;
+            case 'z':
+                *character = '0';
+                break;
+            case '9':
+                *character = '{';
+                break;
+            case '}':
+                *character = '*';
+                break;
+            case '.':
+                *character = '/';
+                break;
+            case '/':
+            case 0x7F: // failsafe: if they've broken out of the intended rotation, return them to 0x20
+                *character = ' ';
+                break;
+            default:
+                *character += 1;
+                break;
+        }
+    } else {
+        // otherwise we have to do some wacky shit
+        switch (*character) {
+            case ' ':
+                *character = 'A';
+                break;
+            case 'F':
+            case 'J':
+            case 'L':
+            case 'R':
+            case '1':
+                *character += 2;
+                break;
+            case 'H':
+                *character = 'l';
+                break;
+            case 'l':
+                *character = 'J';
+                break;
+            case 'O':
+                *character = 'R';
+                break;
+            case 'U':
+                *character = 'X';
+                break;
+            case 'X':
+                *character = '0';
+                break;
+            case '3':
+                *character = '7';
+                break;
+            case '8':
+                *character = '{';
+                break;
+            case '{':
+            case 0x7F: // failsafe: if they've broken out of the intended rotation, return them to 0x20
+                *character = ' ';
+                break;
+            default:
+                *character += 1;
+                break;
+        }
+    }
+}
+
+static void _update_timezone_offset(world_clock_state_t *state) {
     state->current_offset = movement_get_current_timezone_offset_for_zone(state->settings.bit.timezone_index);
 }
 
@@ -41,7 +130,20 @@ void world_clock_face_setup(uint8_t watch_face_index, void ** context_ptr) {
         *context_ptr = malloc(sizeof(world_clock_state_t));
         memset(*context_ptr, 0, sizeof(world_clock_state_t));
         world_clock_state_t *state = (world_clock_state_t *)*context_ptr;
-        state->settings.bit.timezone_index = UTZ_UTC;
+        state->clock_index = world_clock_instances++;
+
+        // load settings from file if it exists
+        char filename[13];
+        sprintf(filename, "wclk_%03d.u32", state->clock_index);
+        if (filesystem_file_exists(filename)) {
+            filesystem_read_file(filename, (char *) &state->settings.reg, sizeof(world_clock_settings_t));
+        } else {
+            // otherwise make all characters blank by default, and set to UTC time
+            state->settings.bit.char_0 = ' ';
+            state->settings.bit.char_1 = ' ';
+            state->settings.bit.char_2 = ' ';
+            state->settings.bit.timezone_index = UTZ_UTC;
+        }
     }
 }
 
@@ -95,8 +197,11 @@ static bool world_clock_face_do_display_mode(movement_event_t event, world_clock
                     date_time.unit.hour %= 12;
                     if (date_time.unit.hour == 0) date_time.unit.hour = 12;
                 }
-                watch_display_character(movement_valid_position_0_chars[state->settings.bit.char_0], 0);
-                watch_display_character(movement_valid_position_0_chars[state->settings.bit.char_1], 1);
+                watch_display_character(state->settings.bit.char_0, 0);
+                watch_display_character(state->settings.bit.char_1, 1);
+                if (watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM) {
+                    watch_display_character(state->settings.bit.char_2, 10);
+                }
                 sprintf(buf, "%2d%2d%02d%02d", date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second);
                 watch_display_text(WATCH_POSITION_TOP_RIGHT, buf);
                 watch_display_text(WATCH_POSITION_HOURS, buf + 2);
@@ -120,18 +225,21 @@ static bool world_clock_face_do_display_mode(movement_event_t event, world_clock
 }
 
 static bool _world_clock_face_do_settings_mode(movement_event_t event, world_clock_state_t *state) {
+    bool is_custom_lcd;
+
     switch (event.event_type) {
         case EVENT_MODE_BUTTON_UP:
-            if (state->backup_register) watch_store_backup_data(state->settings.reg, state->backup_register);
+            persist_world_clock_settings(state);
             movement_move_to_next_face();
             return false;
         case EVENT_LIGHT_BUTTON_DOWN:
             state->current_screen++;
-            if (state->current_screen > 3) {
+            is_custom_lcd = watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM;
+            if ((is_custom_lcd && state->current_screen > 4) || (!is_custom_lcd && state->current_screen > 3)) {
                 movement_request_tick_frequency(1);
                 _update_timezone_offset(state);
                 state->current_screen = 0;
-                if (state->backup_register) watch_store_backup_data(state->settings.reg, state->backup_register);
+                persist_world_clock_settings(state);
                 event.event_type = EVENT_ACTIVATE;
                 return world_clock_face_do_display_mode(event, state);
             }
@@ -139,24 +247,25 @@ static bool _world_clock_face_do_settings_mode(movement_event_t event, world_clo
         case EVENT_ALARM_BUTTON_DOWN:
             switch (state->current_screen) {
                 case 1:
-                    state->settings.bit.char_0++;
-                    if (state->settings.bit.char_0 >= strlen(movement_valid_position_0_chars)) {
-                        state->settings.bit.char_0 = 0;
-                    }
+                    advance_character_at_position(&state->settings.bit.char_0, 0);
                     break;
                 case 2:
-                    state->settings.bit.char_1++;
-                    if (state->settings.bit.char_1 >= strlen(movement_valid_position_1_chars)) {
-                        state->settings.bit.char_1 = 0;
-                    }
+                    advance_character_at_position(&state->settings.bit.char_1, 1);
                     break;
                 case 3:
+                    if (watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM) {
+                        advance_character_at_position(&state->settings.bit.char_2, 2);
+                        break;
+                    }
+                    // fall through
+                case 4:
                     state->settings.bit.timezone_index++;
                     if (state->settings.bit.timezone_index >= NUM_ZONE_NAMES) state->settings.bit.timezone_index = 0;
                     break;
             }
             break;
         case EVENT_TIMEOUT:
+            persist_world_clock_settings(state);
             movement_move_to_face(0);
             break;
         default:
@@ -166,10 +275,11 @@ static bool _world_clock_face_do_settings_mode(movement_event_t event, world_clo
     char buf[13];
 
     watch_clear_colon();
-    sprintf(buf, "%c%c  %s",
-        movement_valid_position_0_chars[state->settings.bit.char_0],
-        movement_valid_position_1_chars[state->settings.bit.char_1],
-        watch_utility_time_zone_name_at_index(state->settings.bit.timezone_index));
+    sprintf(buf, "%c%c  %s%c",
+        state->settings.bit.char_0,
+        state->settings.bit.char_1,
+        watch_utility_time_zone_name_at_index(state->settings.bit.timezone_index),
+        state->settings.bit.char_2);
     watch_clear_indicator(WATCH_INDICATOR_PM);
 
     // blink up the parameter we're setting
@@ -180,7 +290,13 @@ static bool _world_clock_face_do_settings_mode(movement_event_t event, world_clo
                 buf[state->current_screen - 1] = '_';
                 break;
             case 3:
-                sprintf(buf + 3, "       ");
+                if (watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM) {
+                    buf[10] = '_';
+                    break;
+                }
+                // fall through
+            case 4:
+                memcpy(buf + 4, "      ", 6);
                 break;
         }
     }
index 7cb1b252cccfb3b0b0646741c48e05aa42bdca19..c2190d9dd8a45477b8b62fd70f6ae59e3cbde854 100644 (file)
@@ -50,8 +50,9 @@
 
 typedef union {
     struct {
-        uint8_t char_0;
-        uint8_t char_1;
+        char char_0;
+        char char_1;
+        char char_2;
         uint8_t timezone_index;
     } bit;
     uint32_t reg;
@@ -59,7 +60,7 @@ typedef union {
 
 typedef struct {
     world_clock_settings_t settings;
-    uint8_t backup_register;
+    uint8_t clock_index;
     uint8_t current_screen;
     uint32_t previous_date_time;
     int32_t current_offset;
index c6d87d29d8658872054629654c1cc77571b73129..9332bcee9d479ec5a822847de496a07b20327db7 100644 (file)
@@ -218,7 +218,8 @@ void watch_display_text(watch_position_t location, const char *string) {
             watch_display_string(string, 0);
             #pragma GCC diagnostic pop
             if (watch_get_lcd_type()  == WATCH_LCD_TYPE_CUSTOM) {
-                watch_display_character(' ', 10);
+                if (strlen(string) >= 11) watch_display_character(string[10], 10);
+                else watch_display_character(' ', 10);
             }
     }
 }