From 7d129ba6c316585ef7744d9ef1a69404cde4dbc5 Mon Sep 17 00:00:00 2001 From: Joey Castillo Date: Sat, 15 Mar 2025 15:20:22 -0400 Subject: [PATCH] world clock: support longer cities on custom LCD --- movement.c | 3 - movement.h | 2 - watch-faces/clock/world_clock_face.c | 158 +++++++++++++++--- watch-faces/clock/world_clock_face.h | 7 +- .../shared/watch/watch_common_display.c | 3 +- 5 files changed, 143 insertions(+), 30 deletions(-) diff --git a/movement.c b/movement.c index 27bdf21c..7c913577 100644 --- a/movement.c +++ b/movement.c @@ -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); diff --git a/movement.h b/movement.h index 8031daeb..c2343e29 100644 --- a/movement.h +++ b/movement.h @@ -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 diff --git a/watch-faces/clock/world_clock_face.c b/watch-faces/clock/world_clock_face.c index 3bb6cbe2..356f9437 100644 --- a/watch-faces/clock/world_clock_face.c +++ b/watch-faces/clock/world_clock_face.c @@ -28,10 +28,99 @@ #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; } } diff --git a/watch-faces/clock/world_clock_face.h b/watch-faces/clock/world_clock_face.h index 7cb1b252..c2190d9d 100644 --- a/watch-faces/clock/world_clock_face.h +++ b/watch-faces/clock/world_clock_face.h @@ -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; diff --git a/watch-library/shared/watch/watch_common_display.c b/watch-library/shared/watch/watch_common_display.c index c6d87d29..9332bcee 100644 --- a/watch-library/shared/watch/watch_common_display.c +++ b/watch-library/shared/watch/watch_common_display.c @@ -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); } } }