this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Refactor into separate files

alice 3f0d0ac6 81578b04

+569 -439
+77
src/c/clock_beat.c
··· 1 + #include "clock_beat.h" 2 + #include <pebble.h> // Always include pebble for device builds 3 + #include <stdint.h> // For uint32_t, uint64_t 4 + #include <time.h> // For time_t 5 + #include <stdio.h> // For snprintf 6 + 7 + #define HOUR_LENGTH 3600 8 + #define DAY_LENGTH 86400 9 + 10 + // --- Static variables specific to Beat Clock --- 11 + static char s_beat_buffer[7]; // Buffer for Beat time string "@XXX.X\0" 12 + static int last_beat_time = -1; // Cache the last displayed beat time (multiplied by 10) 13 + 14 + // --- Static helper function --- 15 + /** 16 + * Computes the current .beat time * 10 (0-9999) 17 + */ 18 + static int beat(time_t current_seconds_utc) { 19 + time_t now_bmt = current_seconds_utc + HOUR_LENGTH; 20 + // Ensure calculations use appropriate types to avoid overflow/truncation 21 + // Use uint64_t for multiplication before division 22 + uint64_t seconds_in_day = (uint64_t)(now_bmt % DAY_LENGTH); 23 + // Ensure division by DAY_LENGTH is done carefully 24 + int b = (int)((seconds_in_day * 10000ULL) / DAY_LENGTH); 25 + // Clamp the value just in case of edge issues 26 + if (b < 0) b = 0; 27 + if (b > 9999) b = 9999; 28 + return b; 29 + } 30 + 31 + /** 32 + * Calculates the Beat time string and writes it to the buffer. 33 + */ 34 + static void format_beat_time_string(char* buffer, size_t buffer_size, int b) { 35 + int beats_integer = b / 10; // 0-999 36 + int beats_fraction = b % 10; // 0-9 37 + snprintf(buffer, buffer_size, "@%03d.%d", beats_integer, beats_fraction); 38 + } 39 + 40 + // --- Pebble UI Interface Functions --- 41 + 42 + TextLayer* clock_beat_init(GRect bounds, Layer *window_layer) { 43 + TextLayer* layer = text_layer_create(bounds); 44 + text_layer_set_background_color(layer, GColorClear); 45 + text_layer_set_text_color(layer, GColorBlack); 46 + text_layer_set_text(layer, "@--.-"); // Initial placeholder 47 + #define BEAT_FONT FONT_KEY_GOTHIC_24_BOLD // Keep font definitions near usage 48 + text_layer_set_font(layer, fonts_get_system_font(BEAT_FONT)); 49 + text_layer_set_text_alignment(layer, GTextAlignmentCenter); 50 + layer_add_child(window_layer, text_layer_get_layer(layer)); 51 + return layer; 52 + } 53 + 54 + void clock_beat_deinit(TextLayer *layer) { 55 + if (layer) { 56 + text_layer_destroy(layer); 57 + } 58 + } 59 + 60 + void clock_beat_update(TextLayer *layer, time_t current_seconds_utc) { 61 + if (!layer) return; 62 + 63 + int b = beat(current_seconds_utc); 64 + 65 + // Check cache *before* formatting to prevent redundant UI updates 66 + if (b == last_beat_time) { 67 + return; // No change, don't update UI 68 + } 69 + 70 + // Use the helper to format the string into the static buffer 71 + format_beat_time_string(s_beat_buffer, sizeof(s_beat_buffer), b); 72 + 73 + text_layer_set_text(layer, s_beat_buffer); 74 + last_beat_time = b; 75 + } 76 + 77 + // Removed clock_beat_get_time_string wrapper as formatting logic moved
+16
src/c/clock_beat.h
··· 1 + #ifndef CLOCK_BEAT_H 2 + #define CLOCK_BEAT_H 3 + 4 + #include <pebble.h> 5 + #include <stddef.h> // For size_t 6 + 7 + // Initializes the Beat clock layer 8 + TextLayer* clock_beat_init(GRect bounds, Layer *window_layer); 9 + 10 + // Deinitializes the Beat clock layer 11 + void clock_beat_deinit(TextLayer *layer); 12 + 13 + // Updates the Beat clock layer 14 + void clock_beat_update(TextLayer *layer, time_t current_seconds_utc); 15 + 16 + #endif // CLOCK_BEAT_H
+202
src/c/clock_closest_noon.c
··· 1 + #include "clock_closest_noon.h" 2 + #include <pebble.h> // Always include pebble for device builds 3 + #include <stdlib.h> // For rand() 4 + #include <stdint.h> // For uint32_t, int64_t 5 + #include <time.h> // For time_t, struct tm, gmtime 6 + #include <stdbool.h>// For bool 7 + 8 + // Include the generated timezone data structure definitions and list 9 + // This must contain TzInfo, tz_list[], and TZ_LIST_COUNT 10 + #include "tz_list.c" 11 + 12 + #define DAY_LENGTH 86400 13 + #define NOON_SECONDS (12 * 3600L) 14 + 15 + // --- Static variables specific to Closest Noon Clock --- 16 + static char s_closest_noon_buffer[32]; 17 + static int last_closest_noon_update_secs = -1; 18 + 19 + // Cache for stabilization 20 + static int last_chosen_zone_index = -1; // Index in tz_list of the last chosen zone 21 + static const char* last_displayed_name = NULL; // Specific name displayed last time 22 + 23 + // --- Static helper functions --- 24 + /** 25 + * Helper function to find the current noon-hour zone, handle stabilization, 26 + * and calculate display values. Switches proactively to the zone(s) 27 + * closest to 12:00:00 local time. 28 + * 29 + * Returns: true if a zone was found and display values calculated, false otherwise. 30 + * Outputs: Pointers to store the chosen display name, minutes past noon, and seconds past noon. 31 + */ 32 + static bool calculate_noon_zone_display(time_t current_seconds_utc, 33 + const char** out_display_name, 34 + long* out_display_minutes, 35 + long* out_display_seconds) 36 + { 37 + time_t current_time_t = (time_t)current_seconds_utc; 38 + uint32_t utc_seconds_today = current_seconds_utc % DAY_LENGTH; 39 + 40 + // --- Find candidate zones currently in the noon hour (12:00:00 - 12:59:59) --- 41 + typedef struct { 42 + int index; 43 + long local_secs; 44 + } NoonCandidate; 45 + NoonCandidate current_candidates[TZ_LIST_COUNT]; 46 + int current_candidate_count = 0; 47 + long min_local_secs_in_noon_hour = DAY_LENGTH; // Find the earliest time past 12:00 48 + 49 + for (int i = 0; i < TZ_LIST_COUNT; ++i) { 50 + const TzInfo *tz = &tz_list[i]; 51 + bool is_dst_active = false; 52 + // --- Determine if DST is active --- 53 + if (tz->dst_start_utc != 0LL && tz->dst_end_utc != 0LL) { 54 + int64_t start_time = tz->dst_start_utc; 55 + int64_t end_time = tz->dst_end_utc; 56 + int64_t current_time_64 = (int64_t)current_time_t; 57 + if (start_time <= end_time) { 58 + if (current_time_64 >= start_time && current_time_64 < end_time) is_dst_active = true; 59 + } else { 60 + if (current_time_64 >= start_time || current_time_64 < end_time) is_dst_active = true; 61 + } 62 + } 63 + float chosen_offset_hours = is_dst_active ? tz->dst_offset_hours : tz->std_offset_hours; 64 + long offset_seconds = (long)(chosen_offset_hours * 3600.0f); 65 + long local_seconds_today = ((long)utc_seconds_today + offset_seconds) % DAY_LENGTH; 66 + if (local_seconds_today < 0) local_seconds_today += DAY_LENGTH; 67 + 68 + // --- Check if this zone is in the noon hour --- 69 + if (local_seconds_today >= NOON_SECONDS && local_seconds_today < (NOON_SECONDS + 3600)) { 70 + if (current_candidate_count < TZ_LIST_COUNT) { 71 + current_candidates[current_candidate_count].index = i; 72 + current_candidates[current_candidate_count].local_secs = local_seconds_today; 73 + current_candidate_count++; 74 + // Track the minimum local time among candidates 75 + if (local_seconds_today < min_local_secs_in_noon_hour) { 76 + min_local_secs_in_noon_hour = local_seconds_today; 77 + } 78 + } 79 + } 80 + } 81 + 82 + // --- Identify the "Best" Candidates (those with the minimum local time) --- 83 + NoonCandidate best_candidates[TZ_LIST_COUNT]; 84 + int best_candidate_count = 0; 85 + if (current_candidate_count > 0) { 86 + for (int k = 0; k < current_candidate_count; ++k) { 87 + if (current_candidates[k].local_secs == min_local_secs_in_noon_hour) { 88 + if (best_candidate_count < TZ_LIST_COUNT) { 89 + best_candidates[best_candidate_count++] = current_candidates[k]; 90 + } 91 + } 92 + } 93 + } 94 + 95 + // --- Select the zone and name to display --- 96 + int chosen_zone_index = -1; 97 + long chosen_local_seconds = -1; 98 + const char *current_display_name = "Wait..."; 99 + 100 + bool use_last_chosen = false; 101 + if (last_chosen_zone_index != -1 && best_candidate_count > 0) { 102 + // Check if the last chosen zone is among the *best* current candidates 103 + for (int k = 0; k < best_candidate_count; ++k) { 104 + if (best_candidates[k].index == last_chosen_zone_index) { 105 + chosen_zone_index = last_chosen_zone_index; 106 + chosen_local_seconds = best_candidates[k].local_secs; 107 + current_display_name = last_displayed_name; // Reuse the specific name 108 + use_last_chosen = true; 109 + break; 110 + } 111 + } 112 + } 113 + 114 + if (!use_last_chosen) { // If last wasn't valid *among the best*, or first run, or no candidates 115 + if (best_candidate_count > 0) { 116 + // Select an index randomly *from the best candidates* 117 + int list_pos = (best_candidate_count == 1) ? 0 : (rand() % best_candidate_count); 118 + chosen_zone_index = best_candidates[list_pos].index; 119 + chosen_local_seconds = best_candidates[list_pos].local_secs; 120 + 121 + // Select a specific city name from that chosen zone's list 122 + const TzInfo *chosen_tz = &tz_list[chosen_zone_index]; 123 + if (chosen_tz->name_count > 0) { 124 + int name_index = (chosen_tz->name_count == 1) ? 0 : (rand() % chosen_tz->name_count); 125 + if (name_index < chosen_tz->name_count) { 126 + current_display_name = chosen_tz->names[name_index].name; 127 + } else { current_display_name = "ERR:NAME"; } // Safety fallback 128 + } else { current_display_name = "ERR:NO_NM"; } // Safety fallback 129 + 130 + // Update static cache for next time 131 + last_chosen_zone_index = chosen_zone_index; 132 + last_displayed_name = current_display_name; 133 + } else { 134 + // No zones currently in noon hour - reset static cache 135 + last_chosen_zone_index = -1; 136 + last_displayed_name = NULL; 137 + current_display_name = "Wait..."; // Explicitly set placeholder 138 + chosen_zone_index = -1; // Ensure calculation is skipped 139 + } 140 + } 141 + 142 + // --- Calculate display values and return --- 143 + if (chosen_zone_index != -1) { 144 + long seconds_since_noon_in_zone = chosen_local_seconds - NOON_SECONDS; 145 + *out_display_minutes = seconds_since_noon_in_zone / 60; 146 + *out_display_seconds = seconds_since_noon_in_zone % 60; 147 + *out_display_name = current_display_name; 148 + return true; // Success 149 + } else { 150 + *out_display_name = current_display_name; // Return "Wait..." or error 151 + return false; // Indicate failure 152 + } 153 + } 154 + 155 + // --- Interface Functions (Pebble only) --- 156 + 157 + TextLayer* clock_closest_noon_init(GRect bounds, Layer *window_layer) { 158 + TextLayer* layer = text_layer_create(bounds); 159 + text_layer_set_background_color(layer, GColorClear); 160 + text_layer_set_text_color(layer, GColorBlack); 161 + text_layer_set_text(layer, "City:--:--"); // Initial placeholder 162 + #define CLOSEST_FONT FONT_KEY_GOTHIC_18_BOLD // Keep font definitions near usage 163 + text_layer_set_font(layer, fonts_get_system_font(CLOSEST_FONT)); 164 + text_layer_set_text_alignment(layer, GTextAlignmentCenter); 165 + layer_add_child(window_layer, text_layer_get_layer(layer)); 166 + return layer; 167 + } 168 + 169 + void clock_closest_noon_deinit(TextLayer *layer) { 170 + if (layer) { 171 + text_layer_destroy(layer); 172 + } 173 + } 174 + 175 + void clock_closest_noon_update(TextLayer *layer, time_t current_seconds_utc) { 176 + if (!layer) return; 177 + 178 + // Avoid update if time hasn't changed 179 + if (current_seconds_utc == last_closest_noon_update_secs) { 180 + return; 181 + } 182 + 183 + const char *display_name; 184 + long display_minutes; 185 + long display_seconds; 186 + 187 + bool found_zone = calculate_noon_zone_display(current_seconds_utc, 188 + &display_name, 189 + &display_minutes, 190 + &display_seconds); 191 + 192 + if (found_zone) { 193 + snprintf(s_closest_noon_buffer, sizeof(s_closest_noon_buffer), 194 + "%s:%02ld:%02ld", display_name, display_minutes, display_seconds); 195 + } else { 196 + // Use the placeholder/error name returned by the helper 197 + snprintf(s_closest_noon_buffer, sizeof(s_closest_noon_buffer), "%s", display_name); 198 + } 199 + 200 + text_layer_set_text(layer, s_closest_noon_buffer); 201 + last_closest_noon_update_secs = current_seconds_utc; 202 + }
+16
src/c/clock_closest_noon.h
··· 1 + #ifndef CLOCK_CLOSEST_NOON_H 2 + #define CLOCK_CLOSEST_NOON_H 3 + 4 + #include <pebble.h> // Pebble time_t definition and UI types 5 + #include <stddef.h> // For size_t 6 + 7 + // Initializes the Closest Noon clock layer 8 + TextLayer* clock_closest_noon_init(GRect bounds, Layer *window_layer); 9 + 10 + // Deinitializes the Closest Noon clock layer 11 + void clock_closest_noon_deinit(TextLayer *layer); 12 + 13 + // Updates the Closest Noon clock layer 14 + void clock_closest_noon_update(TextLayer *layer, time_t current_seconds_utc); 15 + 16 + #endif // CLOCK_CLOSEST_NOON_H
+103
src/c/clock_noonzone.c
··· 1 + #include "clock_noonzone.h" 2 + #include <pebble.h> 3 + #include <time.h> // For time_t, struct tm, gmtime 4 + #include <string.h> // For NULL definition (consider stddef.h?) 5 + #include <stdio.h> // For snprintf 6 + 7 + // --- Static variables specific to Noon Zone Clock --- 8 + static char s_noonzone_buffer[16]; // Buffer for "NAME:MM:SS\0" 9 + static int last_noonzone_update_secs = -1; 10 + static int last_utc_hour = -1; 11 + static const char *last_zone_name_ptr = NULL; 12 + 13 + // --- Static helper function --- 14 + /** 15 + * Gets the military timezone name for the longitude where it is currently noon. 16 + * Includes caching based on UTC hour. 17 + */ 18 + static const char* get_noon_zone_name(int utc_hour) { 19 + if (utc_hour == last_utc_hour && last_zone_name_ptr != NULL) { 20 + return last_zone_name_ptr; 21 + } 22 + 23 + const char *name = "???"; 24 + switch(utc_hour){ 25 + case 12: name="ZULU"; break; 26 + case 11: name="ALPHA"; break; 27 + case 10: name="BRAVO"; break; 28 + case 9: name="CHARLIE"; break; 29 + case 8: name="DELTA"; break; 30 + case 7: name="ECHO"; break; 31 + case 6: name="FOXTROT"; break; 32 + case 5: name="GOLF"; break; 33 + case 4: name="HOTEL"; break; 34 + case 3: name="JULIET"; break; 35 + case 2: name="KILO"; break; 36 + case 1: name="LIMA"; break; 37 + case 0: name="MIKE"; break; 38 + case 23: name="NOVEMBER"; break; 39 + case 22: name="OSCAR"; break; 40 + case 21: name="PAPA"; break; 41 + case 20: name="QUEBEC"; break; 42 + case 19: name="ROMEO"; break; 43 + case 18: name="SIERRA"; break; 44 + case 17: name="TANGO"; break; 45 + case 16: name="UNIFORM"; break; 46 + case 15: name="VICTOR"; break; 47 + case 14: name="WHISKEY"; break; 48 + case 13: name="X-RAY"; break; 49 + // default: name="???"; // Already initialized 50 + } 51 + last_utc_hour = utc_hour; 52 + last_zone_name_ptr = name; 53 + return name; 54 + } 55 + 56 + // --- Pebble UI Interface Functions --- 57 + 58 + TextLayer* clock_noonzone_init(GRect bounds, Layer *window_layer) { 59 + TextLayer* layer = text_layer_create(bounds); 60 + text_layer_set_background_color(layer, GColorClear); 61 + text_layer_set_text_color(layer, GColorBlack); 62 + text_layer_set_text(layer, "ZONE:--:--"); // Initial placeholder 63 + #define NOONZONE_FONT FONT_KEY_GOTHIC_18_BOLD // Keep font definitions near usage 64 + text_layer_set_font(layer, fonts_get_system_font(NOONZONE_FONT)); 65 + text_layer_set_text_alignment(layer, GTextAlignmentCenter); 66 + layer_add_child(window_layer, text_layer_get_layer(layer)); 67 + return layer; 68 + } 69 + 70 + void clock_noonzone_deinit(TextLayer *layer) { 71 + if (layer) { 72 + text_layer_destroy(layer); 73 + } 74 + } 75 + 76 + void clock_noonzone_update(TextLayer *layer, time_t current_seconds_utc) { 77 + if (!layer) return; 78 + 79 + // Check cache first to avoid unnecessary calculation/string formatting 80 + if (current_seconds_utc == last_noonzone_update_secs) { 81 + return; 82 + } 83 + 84 + // Perform calculations 85 + struct tm *utc_tm = gmtime(&current_seconds_utc); 86 + if (!utc_tm) { 87 + text_layer_set_text(layer, "ERR:TIME"); // Show error on layer 88 + last_noonzone_update_secs = current_seconds_utc; // Cache update time even on error 89 + return; 90 + } 91 + 92 + const char *zone_name = get_noon_zone_name(utc_tm->tm_hour); 93 + 94 + // Format string into static buffer 95 + snprintf(s_noonzone_buffer, sizeof(s_noonzone_buffer), 96 + "%s:%02d:%02d", 97 + zone_name, 98 + utc_tm->tm_min, 99 + utc_tm->tm_sec); 100 + 101 + text_layer_set_text(layer, s_noonzone_buffer); 102 + last_noonzone_update_secs = current_seconds_utc; 103 + }
+16
src/c/clock_noonzone.h
··· 1 + #ifndef CLOCK_NOONZONE_H 2 + #define CLOCK_NOONZONE_H 3 + 4 + #include <pebble.h> 5 + #include <stddef.h> // For size_t 6 + 7 + // Initializes the Noon Zone clock layer 8 + TextLayer* clock_noonzone_init(GRect bounds, Layer *window_layer); 9 + 10 + // Deinitializes the Noon Zone clock layer 11 + void clock_noonzone_deinit(TextLayer *layer); 12 + 13 + // Updates the Noon Zone clock layer 14 + void clock_noonzone_update(TextLayer *layer, time_t current_seconds_utc); 15 + 16 + #endif // CLOCK_NOONZONE_H
+96
src/c/clock_tid.c
··· 1 + #include "clock_tid.h" 2 + #include <pebble.h> 3 + #include <string.h> // For memcpy, memset 4 + #include <stdint.h> // For uint types 5 + #include <stdio.h> // For snprintf 6 + 7 + // --- Static variables specific to TID Clock --- 8 + static const char S32_CHAR[] = "234567abcdefghijklmnopqrstuvwxyz"; 9 + #define S32_CHAR_LEN (sizeof(S32_CHAR) - 1) 10 + static uint64_t last_timestamp = 0; 11 + // Buffer needs to hold 11 (timestamp) + 2 (clock ID) + 1 (null terminator) = 14 chars 12 + static char s_tid_buffer[14]; // Buffer for TID time string "MMMSS\0" 13 + // Remove cache for the old 5-digit display (variable is now unused) 14 + // static int last_tid_time = -1; 15 + 16 + // --- Static helper functions --- 17 + 18 + static char* s32encode_c(uint64_t i, char *buffer, size_t buffer_len) { 19 + if (buffer_len == 0) return NULL; 20 + char *ptr = buffer + buffer_len; 21 + if (i == 0) { 22 + if (ptr > buffer) { *(--ptr) = S32_CHAR[0]; return ptr; } 23 + else { return NULL; } 24 + } 25 + while (i > 0 && ptr > buffer) { 26 + uint8_t remainder = i & 31; 27 + i >>= 5; 28 + *(--ptr) = S32_CHAR[remainder]; 29 + } 30 + return ptr; 31 + } 32 + 33 + static void createRaw_c(uint64_t timestamp, char *tid_buffer, size_t tid_buffer_len) { 34 + if (tid_buffer_len < 14) return; 35 + 36 + char ts_temp_buffer[11]; 37 + char* encoded_ts_start = s32encode_c(timestamp, ts_temp_buffer, sizeof(ts_temp_buffer)); 38 + size_t encoded_ts_len = encoded_ts_start ? (ts_temp_buffer + sizeof(ts_temp_buffer)) - encoded_ts_start : 0; 39 + 40 + memset(tid_buffer, S32_CHAR[0], 11); 41 + if (encoded_ts_len > 0 && encoded_ts_len <= 11) { 42 + memcpy(tid_buffer + (11 - encoded_ts_len), encoded_ts_start, encoded_ts_len); 43 + } else if (encoded_ts_len > 11) { 44 + memcpy(tid_buffer, encoded_ts_start + (encoded_ts_len - 11), 11); 45 + } 46 + 47 + tid_buffer[11] = S32_CHAR[0]; 48 + tid_buffer[12] = S32_CHAR[0]; 49 + tid_buffer[13] = '\0'; 50 + } 51 + 52 + /** 53 + * Generates a TID string for the given time into the provided buffer. 54 + * Ensures monotonicity using a static variable internally. 55 + */ 56 + void clock_tid_get_string(char *tid_buffer, size_t tid_buffer_len, time_t seconds, uint16_t milliseconds) { 57 + if (tid_buffer_len < 14) return; // Ensure buffer is large enough 58 + 59 + uint64_t current_micros = (uint64_t)seconds * 1000000 + (uint64_t)milliseconds * 1000; 60 + 61 + if (current_micros <= last_timestamp) { 62 + current_micros = last_timestamp + 1; 63 + } 64 + last_timestamp = current_micros; 65 + createRaw_c(current_micros, tid_buffer, tid_buffer_len); 66 + } 67 + 68 + // --- Pebble UI Interface Functions --- 69 + 70 + TextLayer* clock_tid_init(GRect bounds, Layer *window_layer) { 71 + TextLayer* layer = text_layer_create(bounds); 72 + text_layer_set_background_color(layer, GColorClear); 73 + text_layer_set_text_color(layer, GColorBlack); 74 + text_layer_set_text(layer, "-----"); // Initial placeholder 75 + #define TID_FONT FONT_KEY_GOTHIC_18_BOLD // Keep font definitions near usage 76 + text_layer_set_font(layer, fonts_get_system_font(TID_FONT)); 77 + text_layer_set_text_alignment(layer, GTextAlignmentCenter); 78 + layer_add_child(window_layer, text_layer_get_layer(layer)); 79 + return layer; 80 + } 81 + 82 + void clock_tid_deinit(TextLayer *layer) { 83 + if (layer) { 84 + text_layer_destroy(layer); 85 + } 86 + } 87 + 88 + void clock_tid_update(TextLayer *layer, time_t current_seconds_utc, uint16_t current_milliseconds) { 89 + if (!layer) return; 90 + 91 + // Generate the full TID string into the static buffer 92 + clock_tid_get_string(s_tid_buffer, sizeof(s_tid_buffer), current_seconds_utc, current_milliseconds); 93 + 94 + // Update the TextLayer 95 + text_layer_set_text(layer, s_tid_buffer); 96 + }
+17
src/c/clock_tid.h
··· 1 + #ifndef CLOCK_TID_H 2 + #define CLOCK_TID_H 3 + 4 + #include <pebble.h> 5 + #include <stddef.h> // For size_t 6 + #include <stdint.h> // For uint16_t 7 + 8 + // Initializes the TID clock layer 9 + TextLayer* clock_tid_init(GRect bounds, Layer *window_layer); 10 + 11 + // Deinitializes the TID clock layer 12 + void clock_tid_deinit(TextLayer *layer); 13 + 14 + // Updates the TID clock layer 15 + void clock_tid_update(TextLayer *layer, time_t current_seconds_utc, uint16_t current_milliseconds); 16 + 17 + #endif // CLOCK_TID_H
+26 -439
src/c/watchface.c
··· 1 1 #include <pebble.h> 2 2 #include <stdlib.h> 3 - #include <string.h> 4 - #include <stdint.h> 5 - #include <time.h> // Required for time_t 6 - #include <math.h> // Required for fabsf 7 - #include <stdbool.h> // Required for bool type 3 + #include <time.h> // Required for time_t, rand(), srand() 8 4 9 - // --- Include Generated Timezone Data --- 10 - // This assumes tz_list.c defines TzInfo, tz_list[], and tz_list_count 11 - #include "tz_list.c" 12 - 13 - // --- Constants --- 14 - #define HOUR_LENGTH 3600 // Seconds in an hour 15 - #define DAY_LENGTH 86400 // Seconds in a day (24 * 3600) 5 + // --- Clock Module Includes --- 6 + #include "clock_beat.h" 7 + #include "clock_noonzone.h" 8 + #include "clock_closest_noon.h" 9 + #include "clock_tid.h" 16 10 17 - // --- Global Variables --- 11 + // --- Window and Layer Globals --- 18 12 static Window *s_main_window; 19 13 static TextLayer *s_beat_layer; // Layer for Swatch Beat Time 20 - static TextLayer *s_tid_layer; // Layer for TID Time (renamed from s_time_layer) 14 + static TextLayer *s_tid_layer; // Layer for TID Time 21 15 static TextLayer *s_noonzone_layer; // Layer for Noon Zone Time 22 16 static TextLayer *s_closest_noon_layer; // Layer for Closest-to-Noon TZ 23 17 24 - static const char S32_CHAR[] = "234567abcdefghijklmnopqrstuvwxyz"; 25 - #define S32_CHAR_LEN (sizeof(S32_CHAR) - 1) // 32 26 - 27 - static uint64_t last_timestamp = 0; 28 - 29 - // Encodes 'i' into 'buffer' (size 'buffer_len'). 30 - // Returns a pointer to the start of the encoded string within the buffer. 31 - // Fills from the end. Result is NOT null-terminated by this function itself. 32 - static char* s32encode_c(uint64_t i, char *buffer, size_t buffer_len) { 33 - if (buffer_len == 0) return NULL; 34 - 35 - char *ptr = buffer + buffer_len; // Point *past* the end 36 - 37 - // Handle 0 case directly if timestamp/clockid can be 0 38 - if (i == 0 && ptr > buffer) { 39 - *(--ptr) = S32_CHAR[0]; // '2' 40 - return ptr; 41 - } 42 - 43 - // Encode the number from right to left 44 - while (i > 0 && ptr > buffer) { 45 - // Optimize: Use bitwise operations for division/modulo by 32 (2^5) 46 - uint8_t remainder = i & 31; // i % 32 47 - i >>= 5; // i / 32 48 - *(--ptr) = S32_CHAR[remainder]; // Place character and move pointer left 49 - } 50 - 51 - // If i is still > 0 here, the buffer was too small for the number. 52 - // This shouldn't happen for timestamp (11 chars) or clockid (2 chars). 53 - 54 - return ptr; // Pointer to the first character written 55 - } 56 - 57 - 58 - // Creates a TID in tid_buffer (size must be >= 14) 59 - static void createRaw_c(uint64_t timestamp, uint16_t clockid, char *tid_buffer, size_t tid_buffer_len) { 60 - if (tid_buffer_len < 14) return; // Need space for 13 chars + null terminator 61 - 62 - // --- Timestamp Encoding (11 chars) --- 63 - char ts_temp_buffer[11]; 64 - char* encoded_ts_start = s32encode_c(timestamp, ts_temp_buffer, sizeof(ts_temp_buffer)); 65 - size_t encoded_ts_len = (ts_temp_buffer + sizeof(ts_temp_buffer)) - encoded_ts_start; 66 - 67 - // Pad beginning with '2' 68 - memset(tid_buffer, '2', 11); 69 - // Copy encoded part to the end of the 11-char section 70 - if (encoded_ts_start && encoded_ts_len > 0) { 71 - if (encoded_ts_len <= 11) { 72 - memcpy(tid_buffer + (11 - encoded_ts_len), encoded_ts_start, encoded_ts_len); 73 - } else { 74 - // Timestamp too large? Copy last 11 chars. 75 - memcpy(tid_buffer, encoded_ts_start + (encoded_ts_len - 11), 11); 76 - } 77 - } 78 - // If timestamp was 0, encoded_ts_len is 1 ('2'), correctly placed by the above. 79 - 80 - 81 - // --- Hardcoded Clock ID ("22") --- 82 - // The new logic always appends "22", ignoring the clockid parameter. 83 - tid_buffer[11] = S32_CHAR[0]; // '2' 84 - tid_buffer[12] = S32_CHAR[0]; // '2' 85 - 86 - // Null-terminate the final string 87 - tid_buffer[13] = '\0'; 88 - } 89 - 90 - // Generates a new TID into tid_buffer (size must be >= 14) 91 - static void now_c(time_t seconds, uint16_t milliseconds, char *tid_buffer, size_t tid_buffer_len) { 92 - if (tid_buffer_len < 14) return; 93 - 94 - uint64_t current_micros = (uint64_t)seconds * 1000000 + (uint64_t)milliseconds * 1000; 95 - 96 - // Ensure monotonicity (at microsecond level) 97 - if (current_micros <= last_timestamp) { 98 - current_micros = last_timestamp + 1; 99 - } 100 - last_timestamp = current_micros; 101 - 102 - // Create the final TID string 103 - // Pass 0 for clockid, although it's ignored by the modified createRaw_c 104 - createRaw_c(current_micros, 0, tid_buffer, tid_buffer_len); 105 - } 106 - 107 - // --- Swatch .beat Time Code --- 108 - static char s_beat_buffer[7]; // Buffer for Beat time string "@XXX.X\0" 109 - static int last_beat_time = -1; // Cache the last displayed beat time (multiplied by 10) 110 - 111 - /** 112 - * Computes the current .beat time * 10 (0-9999) 113 - * @param current_seconds_utc The current time (UTC seconds since epoch) 114 - * @return The current beat time multiplied by 10. 115 - */ 116 - static int beat(time_t current_seconds_utc) { 117 - // Add one hour for Biel Mean Time (BMT) 118 - time_t now_bmt = current_seconds_utc + HOUR_LENGTH; 119 - // Calculate seconds into the current BMT day (use unsigned for safety) 120 - uint32_t day_now_bmt = now_bmt % DAY_LENGTH; 121 - 122 - // Calculate beats * 10. 123 - // Cast intermediate multiplication to 64-bit to prevent overflow. 124 - int b = (int)(((uint64_t)day_now_bmt * 10000) / DAY_LENGTH); 125 - 126 - // Clamp result just in case of edge cases (0 - 9999) 127 - if (b > 9999) b = 9999; 128 - return b; 129 - } 130 - 131 - /** 132 - * Updates the beat time TextLayer if the time has changed. 133 - * @param current_seconds_utc The current time (UTC seconds since epoch) 134 - */ 135 - static void update_beat_time(time_t current_seconds_utc) { 136 - int b = beat(current_seconds_utc); // b is 0-9999 137 - 138 - // Only update the layer if the value has changed 139 - if (b == last_beat_time) { 140 - return; 141 - } 142 - 143 - int beats_integer = b / 10; // 0-999 144 - int beats_fraction = b % 10; // 0-9 145 - 146 - // Format as @XXX.X (e.g., @083.3, @999.9) 147 - snprintf(s_beat_buffer, sizeof(s_beat_buffer), "@%03d.%d", beats_integer, beats_fraction); 148 - 149 - // Update the TextLayer 150 - text_layer_set_text(s_beat_layer, s_beat_buffer); 151 - 152 - // Cache the newly displayed value 153 - last_beat_time = b; 154 - } 155 - 156 - // --- Noon Zone Time Code --- 157 - static char s_noonzone_buffer[16]; // Buffer for "NAME:MM:SS\0" 158 - static int last_noonzone_update_secs = -1; // Cache full secs for update check 159 - static int last_utc_hour = -1; // Cache hour for zone name lookup 160 - static const char *last_zone_name_ptr = NULL; // Cache pointer to zone name string 161 - 162 - /** 163 - * Gets the military timezone name for the longitude where it is currently noon, 164 - * based on the provided UTC hour. 165 - * Uses caching to avoid repeated lookups for the same hour. 166 - */ 167 - static const char* get_noon_zone_name(int utc_hour) { 168 - // Check cache first 169 - if (utc_hour == last_utc_hour && last_zone_name_ptr != NULL) { 170 - return last_zone_name_ptr; 171 - } 172 - 173 - const char *name = "???"; // Default for unexpected hours 174 - switch(utc_hour){ 175 - // Cases map UTC hour to the zone where it's noon 176 - case 12: name="ZULU"; break; 177 - case 11: name="ALPHA"; break; 178 - case 10: name="BRAVO"; break; 179 - case 9: name="CHARLIE"; break; 180 - case 8: name="DELTA"; break; 181 - case 7: name="ECHO"; break; 182 - case 6: name="FOXTROT"; break; 183 - case 5: name="GOLF"; break; 184 - case 4: name="HOTEL"; break; 185 - // INDIA is skipped 186 - case 3: name="JULIET"; break; 187 - case 2: name="KILO"; break; 188 - case 1: name="LIMA"; break; 189 - case 0: name="MIKE"; break; // or YANKEE 190 - // Other half of the day (wrapping around) 191 - case 23: name="NOVEMBER"; break; 192 - case 22: name="OSCAR"; break; 193 - case 21: name="PAPA"; break; 194 - case 20: name="QUEBEC"; break; 195 - case 19: name="ROMEO"; break; 196 - case 18: name="SIERRA"; break; 197 - case 17: name="TANGO"; break; 198 - case 16: name="UNIFORM"; break; 199 - case 15: name="VICTOR"; break; 200 - case 14: name="WHISKEY"; break; 201 - case 13: name="X-RAY"; break; 202 - } 203 - 204 - // Update cache 205 - last_utc_hour = utc_hour; 206 - last_zone_name_ptr = name; 207 - return name; 208 - } 209 - 210 - /** 211 - * Updates the Noon Zone time TextLayer if the time (seconds) has changed. 212 - */ 213 - static void update_noonzone_time(time_t current_seconds_utc) { 214 - // Check if the second has changed since the last update 215 - if (current_seconds_utc == last_noonzone_update_secs) { 216 - return; 217 - } 218 - 219 - struct tm *utc_tm = gmtime(&current_seconds_utc); 220 - if (!utc_tm) { // Check if gmtime failed 221 - return; 222 - } 223 - 224 - const char *zone_name = get_noon_zone_name(utc_tm->tm_hour); 225 - 226 - // Format as NAME:MM:SS 227 - snprintf(s_noonzone_buffer, sizeof(s_noonzone_buffer), 228 - "%s:%02d:%02d", 229 - zone_name, 230 - utc_tm->tm_min, 231 - utc_tm->tm_sec); 232 - 233 - text_layer_set_text(s_noonzone_layer, s_noonzone_buffer); 234 - 235 - // Cache the time of this update 236 - last_noonzone_update_secs = current_seconds_utc; 237 - } 238 - 239 - // --- Closest to Noon Timezone Code --- 240 - #define NOON_SECONDS (12 * 3600L) // Noon in seconds past midnight 241 - static char s_closest_noon_buffer[32]; // Buffer for "City Name:MM:SS\0" 242 - static int last_closest_noon_update_secs = -1; // Cache based on seconds 243 - static int last_closest_candidate_indices[TZ_LIST_COUNT]; // Cache indices of previous winners 244 - static int last_closest_candidate_count = 0; // Cache count of previous winners 245 - static const char* last_chosen_closest_name = NULL; // Cache the specific name pointer displayed 246 - 247 - /** 248 - * Comparison function for qsort (integers). 249 - */ 250 - static int compare_ints(const void *a, const void *b) { 251 - return (*(int*)a - *(int*)b); 252 - } 253 - 254 - /** 255 - * Checks if two arrays of integers (candidate indices) are identical. 256 - * Assumes arrays are sorted beforehand. 257 - */ 258 - static bool are_candidate_sets_equal(int* set1, int count1, int* set2, int count2) { 259 - if (count1 != count2) { 260 - return false; 261 - } 262 - // Assumes counts are equal now 263 - for (int i = 0; i < count1; ++i) { 264 - if (set1[i] != set2[i]) { 265 - return false; 266 - } 267 - } 268 - return true; 269 - } 270 - 271 - /** 272 - * Updates the Closest-to-Noon timezone TextLayer if the second has changed. 273 - */ 274 - static void update_closest_noon_time(time_t current_seconds_utc) { 275 - // --- Caching --- 276 - if (current_seconds_utc == last_closest_noon_update_secs) { 277 - return; 278 - } 279 - 280 - // --- Get Current UTC MM:SS --- 281 - struct tm *utc_tm = gmtime(&current_seconds_utc); 282 - if (!utc_tm) return; 283 - int utc_min = utc_tm->tm_min; 284 - int utc_sec = utc_tm->tm_sec; 285 - 286 - // --- Calculate UTC seconds past midnight --- 287 - // Note: tm_yday is 0-365, time_t is secs since epoch. Simpler to use modulo. 288 - uint32_t utc_seconds_today = current_seconds_utc % DAY_LENGTH; 289 - 290 - // --- Find Timezone Closest to Noon --- 291 - long min_diff_secs = DAY_LENGTH; // Init with impossibly large difference 292 - int current_candidate_indices[TZ_LIST_COUNT]; // Store indices of current winners 293 - int current_candidate_count = 0; 294 - 295 - for (int i = 0; i < TZ_LIST_COUNT; ++i) { 296 - // Calculate offset in seconds (handle float) 297 - long offset_seconds = (long)(tz_list[i].offset_hours * 3600.0f); 298 - 299 - // Calculate local time in seconds past midnight (handle wrap around DAY_LENGTH) 300 - long local_seconds_today = (long)utc_seconds_today + offset_seconds; 301 - // Modulo that handles negative results correctly 302 - local_seconds_today = (local_seconds_today % DAY_LENGTH + DAY_LENGTH) % DAY_LENGTH; 303 - 304 - // Calculate the shortest difference to noon (around the 24h clock) 305 - long diff1 = labs(local_seconds_today - NOON_SECONDS); // labs for long 306 - long current_min_diff = (diff1 <= DAY_LENGTH / 2) ? diff1 : DAY_LENGTH - diff1; 307 - 308 - // --- Update candidate list --- 309 - if (current_min_diff < min_diff_secs) { 310 - // New minimum found 311 - min_diff_secs = current_min_diff; 312 - current_candidate_indices[0] = i; 313 - current_candidate_count = 1; 314 - } else if (current_min_diff == min_diff_secs) { 315 - // Tie found, add to candidates (if space permits - should be fine) 316 - if (current_candidate_count < TZ_LIST_COUNT) { // Safety check 317 - current_candidate_indices[current_candidate_count++] = i; 318 - } 319 - } 320 - } 321 - 322 - // --- Select Final Timezone and Name --- 323 - const char *final_name = "???"; 324 - 325 - // Sort current candidates for comparison 326 - if (current_candidate_count > 1) { 327 - qsort(current_candidate_indices, current_candidate_count, sizeof(int), compare_ints); 328 - } 329 - 330 - // Check if the winning set is the same as last time 331 - bool reuse_last_name = false; 332 - if (last_chosen_closest_name != NULL) { // Only reuse if we have a previous name 333 - // Assume last_closest_candidate_indices is already sorted from previous run 334 - if (are_candidate_sets_equal(current_candidate_indices, current_candidate_count, 335 - last_closest_candidate_indices, last_closest_candidate_count)) { 336 - reuse_last_name = true; 337 - final_name = last_chosen_closest_name; // Tentatively reuse 338 - } 339 - } 340 - 341 - // If not reusing, or first time, perform selection 342 - if (!reuse_last_name) { 343 - if (current_candidate_count > 0) { 344 - int chosen_list_index; 345 - if (current_candidate_count == 1) { 346 - chosen_list_index = current_candidate_indices[0]; 347 - } else { 348 - // Randomly select among tied candidates 349 - chosen_list_index = current_candidate_indices[rand() % current_candidate_count]; 350 - } 351 - 352 - // Select random name if multiple exist for the chosen offset 353 - const TzInfo *chosen_tz = &tz_list[chosen_list_index]; 354 - if (chosen_tz->name_count > 0) { 355 - int name_index = (chosen_tz->name_count == 1) ? 0 : (rand() % chosen_tz->name_count); 356 - if (name_index < chosen_tz->name_count) { // Bounds check 357 - final_name = chosen_tz->names[name_index].name; 358 - } 359 - } 360 - 361 - // --- Update Cache for next time --- 362 - last_chosen_closest_name = final_name; // Cache the chosen name pointer 363 - // Copy current candidates to last candidates cache (already sorted) 364 - memcpy(last_closest_candidate_indices, current_candidate_indices, current_candidate_count * sizeof(int)); 365 - last_closest_candidate_count = current_candidate_count; 366 - 367 - } else { 368 - // Should not happen if tz_list is not empty, but handle defensively 369 - last_chosen_closest_name = NULL; // Reset cache if no candidates found 370 - last_closest_candidate_count = 0; 371 - } 372 - } // end selection block 373 - 374 - // --- Format Output and Update Layer --- 375 - // Use the determined final_name (either reused or newly selected) 376 - snprintf(s_closest_noon_buffer, sizeof(s_closest_noon_buffer), 377 - "%s:%02d:%02d", final_name, utc_min, utc_sec); 378 - 379 - text_layer_set_text(s_closest_noon_layer, s_closest_noon_buffer); 380 - 381 - // Cache the update time 382 - last_closest_noon_update_secs = current_seconds_utc; 383 - } 384 - 385 - // --- TID Time Update --- 386 - static char s_tid_buffer[14]; // Buffer for TID string (13 chars + null) 387 - 388 - /** 389 - * Updates the TID time TextLayer. 390 - * Uses provided time components for efficiency. 391 - */ 392 - static void update_tid_time(time_t seconds, uint16_t milliseconds) { 393 - // Generate the current TID into the buffer using provided time 394 - now_c(seconds, milliseconds, s_tid_buffer, sizeof(s_tid_buffer)); 395 - 396 - // Display this TID on the TextLayer 397 - // Note: TID changes every microsecond theoretically, 398 - // so we update the text layer every second regardless of visible change. 399 - text_layer_set_text(s_tid_layer, s_tid_buffer); 400 - } 401 - 402 - 403 18 // --- Pebble Window Management --- 404 19 405 20 // Handles updates from the TickTimerService ··· 410 25 time_ms(&seconds, &milliseconds); 411 26 412 27 // Update both time displays 413 - update_beat_time(seconds); 414 - update_tid_time(seconds, milliseconds); 415 - update_noonzone_time(seconds); 416 - update_closest_noon_time(seconds); 28 + clock_beat_update(s_beat_layer, seconds); 29 + clock_tid_update(s_tid_layer, seconds, milliseconds); 30 + clock_noonzone_update(s_noonzone_layer, seconds); 31 + clock_closest_noon_update(s_closest_noon_layer, seconds); 417 32 } 418 33 419 34 static void main_window_load(Window *window) { ··· 449 64 int16_t tid_y = current_y; 450 65 451 66 // Create Beat Time TextLayer (Top) 452 - s_beat_layer = text_layer_create( 453 - GRect(0, beat_y, bounds.size.w, beat_h)); 454 - text_layer_set_background_color(s_beat_layer, GColorClear); 455 - text_layer_set_text_color(s_beat_layer, GColorBlack); 456 - text_layer_set_text(s_beat_layer, "@--.-"); // Initial placeholder 457 - text_layer_set_font(s_beat_layer, fonts_get_system_font(BEAT_FONT)); 458 - text_layer_set_text_alignment(s_beat_layer, GTextAlignmentCenter); 459 - layer_add_child(window_layer, text_layer_get_layer(s_beat_layer)); 67 + s_beat_layer = clock_beat_init(GRect(0, beat_y, bounds.size.w, beat_h), window_layer); 460 68 461 69 // Create Noon Zone Time TextLayer (Middle) 462 - s_noonzone_layer = text_layer_create( 463 - GRect(0, noonzone_y, bounds.size.w, noonzone_h)); 464 - text_layer_set_background_color(s_noonzone_layer, GColorClear); 465 - text_layer_set_text_color(s_noonzone_layer, GColorBlack); 466 - text_layer_set_text(s_noonzone_layer, "ZONE:--:--"); // Initial placeholder 467 - text_layer_set_font(s_noonzone_layer, fonts_get_system_font(NOONZONE_FONT)); 468 - text_layer_set_text_alignment(s_noonzone_layer, GTextAlignmentCenter); 469 - layer_add_child(window_layer, text_layer_get_layer(s_noonzone_layer)); 70 + s_noonzone_layer = clock_noonzone_init(GRect(0, noonzone_y, bounds.size.w, noonzone_h), window_layer); 470 71 471 72 // Create Closest Noon Time TextLayer (Middle-Bottom) 472 - s_closest_noon_layer = text_layer_create( 473 - GRect(0, closest_y, bounds.size.w, closest_h)); 474 - text_layer_set_background_color(s_closest_noon_layer, GColorClear); 475 - text_layer_set_text_color(s_closest_noon_layer, GColorBlack); 476 - text_layer_set_text(s_closest_noon_layer, "City:--:--"); // Initial placeholder 477 - text_layer_set_font(s_closest_noon_layer, fonts_get_system_font(CLOSEST_FONT)); 478 - text_layer_set_text_alignment(s_closest_noon_layer, GTextAlignmentCenter); 479 - layer_add_child(window_layer, text_layer_get_layer(s_closest_noon_layer)); 73 + s_closest_noon_layer = clock_closest_noon_init(GRect(0, closest_y, bounds.size.w, closest_h), window_layer); 480 74 481 75 // Create TID TextLayer (Bottom) 482 - s_tid_layer = text_layer_create( 483 - GRect(0, tid_y, bounds.size.w, tid_h)); 484 - text_layer_set_background_color(s_tid_layer, GColorClear); 485 - text_layer_set_text_color(s_tid_layer, GColorBlack); 486 - text_layer_set_text(s_tid_layer, "loading tid..."); // Initial placeholder 487 - text_layer_set_font(s_tid_layer, fonts_get_system_font(TID_FONT)); 488 - text_layer_set_text_alignment(s_tid_layer, GTextAlignmentCenter); 489 - layer_add_child(window_layer, text_layer_get_layer(s_tid_layer)); 76 + s_tid_layer = clock_tid_init(GRect(0, tid_y, bounds.size.w, tid_h), window_layer); 490 77 } 491 78 492 79 static void main_window_unload(Window *window) { 493 80 // Destroy TextLayers 494 - text_layer_destroy(s_beat_layer); 495 - text_layer_destroy(s_tid_layer); // Renamed from s_time_layer 496 - text_layer_destroy(s_noonzone_layer); 497 - text_layer_destroy(s_closest_noon_layer); 81 + clock_beat_deinit(s_beat_layer); 82 + clock_noonzone_deinit(s_noonzone_layer); 83 + clock_closest_noon_deinit(s_closest_noon_layer); 84 + clock_tid_deinit(s_tid_layer); 498 85 } 499 86 500 87 static void init() { 88 + // Seed random number generator (used by closest noon clock) 89 + srand(time(NULL)); 90 + 501 91 // Create main Window element and assign to pointer 502 92 s_main_window = window_create(); 503 93 ··· 512 102 // Get initial time and update display immediately 513 103 time_t seconds; 514 104 uint16_t milliseconds; 515 - time_ms(&seconds, &milliseconds); 516 - update_beat_time(seconds); // Initial beat time 517 - update_tid_time(seconds, milliseconds); // Initial TID time 518 - update_noonzone_time(seconds); // Initial noon zone time 519 - update_closest_noon_time(seconds); // Initial closest noon time 520 - 105 + time_ms(&seconds, &milliseconds); // Call only once 106 + // Initial updates can be skipped if layers show placeholders, 107 + // tick_handler will update them shortly after load. 521 108 522 109 // Register with TickTimerService to update every second 523 110 tick_timer_service_subscribe(SECOND_UNIT, tick_handler);