From 84b26fa5088c3ee54843b6877aae59764e4318fd Mon Sep 17 00:00:00 2001 From: alanacheff Date: Sun, 6 Oct 2024 21:25:57 +0000 Subject: [PATCH] Upload files to "src/c" --- src/c/options.c | 78 +++++++ src/c/options.h | 5 + src/c/tictoc_wd.c | 572 ++++++++++++++++++++++++++++++++++++++++++++++ src/c/tictoc_wd.h | 53 +++++ src/c/weather.c | 245 ++++++++++++++++++++ 5 files changed, 953 insertions(+) create mode 100644 src/c/options.c create mode 100644 src/c/options.h create mode 100644 src/c/tictoc_wd.c create mode 100644 src/c/tictoc_wd.h create mode 100644 src/c/weather.c diff --git a/src/c/options.c b/src/c/options.c new file mode 100644 index 0000000..8e5ca60 --- /dev/null +++ b/src/c/options.c @@ -0,0 +1,78 @@ +#include +#include "options.h" +#include "constants.h" +#include "tictoc_wd.h" + +extern Options s_options; + +void init_options() { + + if (persist_exists(KEY_OPTIONS)) { + persist_read_data(KEY_OPTIONS, &s_options, sizeof(s_options)); + } + else { + s_options.shake_for_lohi = DEFAULT_SHAKE_FOR_LOHI; + s_options.display_weather = DEFAULT_DISPLAY_WEATHER; + s_options.use_celsius = DEFAULT_USE_CELSIUS; + s_options.weather_use_GPS = DEFAULT_WEATHER_USE_GPS; + memset(s_options.weather_location, 0,sizeof(s_options.weather_location)); + s_options.weather_frequency = DEFAULT_WEATHER_FREQUENCY; + s_options.min_since_last_forecast = DEFAULT_MIN_SINCE_WEATHER_UPDATE; + s_options.background_color = DEFAULT_BACKGROUND_COLOR; + s_options.text_color = DEFAULT_TEXT_COLOR; + s_options.hour_hand_color = DEFAULT_HOUR_HAND_COLOR; + s_options.minute_hand_color = DEFAULT_MINUTE_HAND_COLOR; + s_options.hour_markers_color = DEFAULT_HOUR_MARKERS_COLOR; + s_options.minor_markers_color = DEFAULT_MINOR_MARKERS_COLOR; + s_options.dots_color = DEFAULT_DOTS_COLOR; + s_options.readability = DEFAULT_READABILITY; + s_options.condition_code = DEFAULT_CONDITION_CODE; + s_options.display_digital_time = DEFAULT_DISPLAY_DIGITAL_TIME; + s_options.display_date = DEFAULT_DISPLAY_DATE; + s_options.display_month = DEFAULT_DISPLAY_MONTH; + s_options.vibrate_bt_status = DEFAULT_VIBRATE_BT_STATUS; + s_options.use_thin_hands = DEFAULT_USE_THIN_HANDS; + s_options.display_battery = DEFAULT_DISPLAY_BATTERY; + s_options.display_minute_lines = DEFAULT_DISPLAY_MINUTE_LINES; + memset(s_options.conditions, 0,sizeof(s_options.conditions)); + snprintf(s_options.last_weather_update12hr, sizeof(s_options.last_weather_update12hr), "--:--"); + snprintf(s_options.last_weather_update24hr, sizeof(s_options.last_weather_update24hr), "--:--"); + //persist_write_data(KEY_OPTIONS, &s_options, sizeof(s_options)); + } + #if DEBUG + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.shake_for_lohi: %d", s_options.shake_for_lohi); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.tempF: %d", s_options.tempF); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.tempFLo: %d", s_options.tempFLo); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.tempFHi: %d", s_options.tempFHi); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.tempC: %d", s_options.tempC); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.tempCLo: %d", s_options.tempCLo); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.tempCHi: %d", s_options.tempCHi); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.display_weather: %d", s_options.display_weather); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.weather_use_GPS: %d", s_options.weather_use_GPS); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.weather_location: %s", s_options.weather_location); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.use_celsius: %d", s_options.use_celsius); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.weather_frequency: %d", s_options.weather_frequency); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.min_since_last_forecast: %d", s_options.min_since_last_forecast); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.background_color: %d", s_options.background_color); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.text_color: %d", s_options.text_color); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.hour_hand_color: %d", s_options.hour_hand_color); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.minute_hand_color: %d", s_options.minute_hand_color); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.hour_markers_color: %d", s_options.hour_markers_color); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.minor_markers_color: %d", s_options.minor_markers_color); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.dots_color: %d", s_options.dots_color); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.readability: %d", s_options.readability); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.condition_code: %d", s_options.condition_code); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.bluetooth_state: %d", s_options.bluetooth_state); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.display_digital_time: %d", s_options.display_digital_time); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.display_date: %d", s_options.display_date); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.display_month: %d", s_options.display_month); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.vibrate_bt_status: %d", s_options.vibrate_bt_status); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.use_thin_hands: %d", s_options.use_thin_hands); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.display_battery: %d", s_options.display_battery); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.display_minute_lines: %d", s_options.display_minute_lines); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.conditions: %s", s_options.conditions); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.last_weather_update12hr: %s", s_options.last_weather_update12hr); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.last_weather_update24hr: %s", s_options.last_weather_update24hr); + APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: options sizeof(s_options) %zu", sizeof(s_options)); + #endif +} diff --git a/src/c/options.h b/src/c/options.h new file mode 100644 index 0000000..f1d00d9 --- /dev/null +++ b/src/c/options.h @@ -0,0 +1,5 @@ +#pragma once + +#define KEY_OPTIONS 99 + + void init_options(); \ No newline at end of file diff --git a/src/c/tictoc_wd.c b/src/c/tictoc_wd.c new file mode 100644 index 0000000..0c873d1 --- /dev/null +++ b/src/c/tictoc_wd.c @@ -0,0 +1,572 @@ +#include +#include "tictoc_wd.h" +#include "constants.h" +#include "options.h" +#include "weather.h" +#include "date.h" +#include "messaging.h" +#include "digital_time.h" +#include "battery.h" + +#define SCREEN_SHOT_HR 21 +#define SCREEN_SHOT_MIN 22 + +#define ANTIALIASING true + +#define HAND_MARGIN 18 +#define FINAL_RADIUS 80 + +#define REGULAR_HANDS_WIDTH 7 +#define THIN_HANDS_WIDTH 5 + +#define ANIMATION_DURATION 500 +#define ANIMATION_DELAY 600 + +#define HOUR_BAR_MARGIN 5 + +#define HAND_LENGTH_SEC_PBL_ROUND 75 +#define HAND_LENGTH_SEC 65 +#define SECONDS_HAND_DISPLAY_TIME_SECS 18 +int g_display_seconds_hand_count = 0; //stores brief counter to display seconds hand + +Options s_options; + +//stores weather retry counts (incremented in second tick handler). +//Stop retry count at DEFAULT_MAX_WEATHER_RETRY_COUNT +int g_weather_retry_count = 0; + +typedef struct { + int hours; + int minutes; + int seconds; +} Time; + +static GPoint s_center; +static Time s_last_time, s_anim_time; +static int s_radius = 0, s_color_channels[3]; +static bool s_animating = false; + +void set_text_layers_color(GColor textColor) { + text_layer_set_text_color(weather_layer1, textColor); + #if defined(PBL_ROUND) + text_layer_set_text_color(weather_layer2, textColor); + text_layer_set_text_color(weather_layer_center, textColor); + #endif + text_layer_set_text_color(date_layer_center, textColor); + text_layer_set_text_color(battery_layer, textColor); +} + +static void init_window_background_color(){ + + window_set_background_color(s_main_window, GColorBlack); + window_set_background_color(s_main_window, GColorFromHEX(s_options.background_color)); +} + +void init_static_row(TextLayer *label, GFont font) { + + text_layer_set_background_color(label, GColorClear); + text_layer_set_text_color(label, GColorWhite); + + text_layer_set_text_color(label, GColorFromHEX(s_options.text_color)); + + if (font) { + text_layer_set_font(label, font); + } +} + +/*************************** AnimationImplementation **************************/ + +static void animation_started(Animation *anim, void *context) { + s_animating = true; +} + +static void animation_stopped(Animation *anim, bool stopped, void *context) { + s_animating = false; +} + +static void animate(int duration, int delay, AnimationImplementation *implementation, bool handlers) { + Animation *anim = animation_create(); + animation_set_duration(anim, duration); + animation_set_delay(anim, delay); + animation_set_curve(anim, AnimationCurveEaseInOut); + animation_set_implementation(anim, implementation); + if(handlers) { + animation_set_handlers(anim, (AnimationHandlers) { + .started = animation_started, + .stopped = animation_stopped + }, NULL); + } + animation_schedule(anim); +} + +/************************************ UI **************************************/ + +static void bt_handler(bool isConnected) { + + int current_bluetooth_state = 0; //default bluetooth disconnected + if (isConnected) { + current_bluetooth_state = 1; //bluetooth connected + } + + //if bluetooth was previously connected and is now disconnected + if (s_options.bluetooth_state == 1 && + current_bluetooth_state == 0) { + + if (s_options.vibrate_bt_status) { + vibes_short_pulse(); //vibrate on bt disconnect, if option enabled + + if(s_canvas_layer) { + layer_mark_dirty(s_canvas_layer); + } + } + } + + //if bluetooth was previously disconnected and is now connected + if (s_options.bluetooth_state == 0 && + current_bluetooth_state == 1) { + + //clear weather conditions and reset weather retry count to force weather update + memset(s_options.conditions, 0,sizeof(s_options.conditions)); + s_options.condition_code = DEFAULT_CONDITION_CODE; + g_weather_retry_count = 0; + } + + //store bluetooth state + s_options.bluetooth_state = current_bluetooth_state; +} + +static void tap_handler(AccelAxisType axis, int32_t direction) { + + if (s_options.display_seconds_hand > 0) { + time_t t = time(NULL); + struct tm *time_now = localtime(&t); + + s_last_time.seconds = time_now->tm_sec; + + g_display_seconds_hand_count = SECONDS_HAND_DISPLAY_TIME_SECS; + } + + if (s_options.shake_for_lohi == 1) { + //Display Lo Hi temp info; + display_lohi_weather_info(); + } +} + +//every five seconds +static void handle_5second_tick(struct tm *tick_time, TimeUnits units_changed) { + + //if conditions blank (no current weather info) + if(strlen(s_options.conditions) == 0 && + g_weather_retry_count < DEFAULT_MAX_WEATHER_RETRY_COUNT) { + + //add one to weather retry count - stop trying this app session at DEFAULT_MAX_WEATHER_RETRY_COUNT + ++g_weather_retry_count; + + //reset time since last forecast timer + s_options.min_since_last_forecast = 0; + + //get updated weather + send(KEY_GET_WEATHER, 1, KEY_WEATHER_USE_GPS, s_options.weather_use_GPS, KEY_WEATHER_LOCATION, s_options.weather_location); + + //update weather layer + update_weather_layer(); + } + //APP_LOG(APP_LOG_LEVEL_DEBUG, "heap_bytes_free:heap_bytes_used %zu: %zu", heap_bytes_free(), heap_bytes_used()); +} + +//every second +static void handle_second_tick(struct tm *tick_time, TimeUnits units_changed) { + +if((s_options.display_seconds_hand == 1 && g_display_seconds_hand_count > 0) || + s_options.display_seconds_hand == 2) { + + s_last_time.seconds = tick_time->tm_sec; + + if(s_canvas_layer) { + layer_mark_dirty(s_canvas_layer); + } + } +} + +//every minute +static void handle_minute_tick(struct tm *tick_time, TimeUnits units_changed) { + + // Store time + s_last_time.hours = tick_time->tm_hour; + s_last_time.hours -= (s_last_time.hours > 12) ? 12 : 0; + s_last_time.minutes = tick_time->tm_min; + + for(int i = 0; i < 3; i++) { + s_color_channels[i] = 0; + } + + // Redraw + if(s_canvas_layer) { + layer_mark_dirty(s_canvas_layer); + } + + if (units_changed & HOUR_UNIT) { + //vibes_double_pulse(); //hourly vibration + } + + update_date_layer(); //check/update date layer + update_digital_time_layer(); //check/update digital_time_layer + update_battery_layer(); //check/update update_battery_layer + + //get minutes since last weather update + time_t t = time(NULL); + int min_since_last_update = (difftime(t, s_options.last_update) / 60) + 1; + + APP_LOG(APP_LOG_LEVEL_DEBUG, " (Last update %s) min_since_last_update:weather_frequency - %d:%d", s_options.last_weather_update24hr, min_since_last_update, s_options.weather_frequency); + //APP_LOG(APP_LOG_LEVEL_DEBUG, "handle_minute_tick strlen(s_options.conditions): %zu", strlen(s_options.conditions)); + //if min_since_last_forecast greater than or equal to weatherUpdateFrequency + if(min_since_last_update >= s_options.weather_frequency || + strlen(s_options.conditions) == 0) { + + s_options.min_since_last_forecast = 0; + + //get updated weather + send(KEY_GET_WEATHER, 1, KEY_WEATHER_USE_GPS, s_options.weather_use_GPS, KEY_WEATHER_LOCATION, s_options.weather_location); + + //update weather layer + update_weather_layer(); + } +} + +void handle_tick(struct tm *tick_time, TimeUnits units_changed) { + + if (units_changed & SECOND_UNIT) { + handle_second_tick(tick_time, units_changed); + + if (tick_time->tm_sec % 5 == 0) { + handle_5second_tick(tick_time, units_changed); + } + } + if (units_changed & MINUTE_UNIT) { + handle_minute_tick(tick_time, units_changed); + } +} + +static int hours_to_minutes(int hours_out_of_12) { + return (int)(float)(((float)hours_out_of_12 / 12.0F) * 60.0F); +} + +static void update_proc(Layer *layer, GContext *ctx) { + + //GRect layer_frame = layer_get_frame(window_get_root_layer(s_main_window)); + GRect layer_frame = layer_get_bounds(window_get_root_layer(s_main_window)); + const int16_t width = layer_frame.size.w; + const int16_t height = layer_frame.size.h; + + graphics_context_set_stroke_color(ctx, GColorWhite); + + #ifdef PBL_SDK_3 + graphics_context_set_antialiased(ctx, ANTIALIASING); + #endif + + /************************************ Minute lines **************************************/ + + if (s_options.display_minute_lines) { + + for (int i = 0; i < 60; i++) { + // hour bar + if (i % 1 == 0 && i / 1 != -1) { + GPoint minute_line = (GPoint) { + .x = (int16_t)(sin_lookup(TRIG_MAX_ANGLE * i / 60) * (int32_t)(s_radius - HOUR_BAR_MARGIN) / TRIG_MAX_RATIO) + s_center.x, + .y = (int16_t)(-cos_lookup(TRIG_MAX_ANGLE * i / 60) * (int32_t)(s_radius - HOUR_BAR_MARGIN) / TRIG_MAX_RATIO) + s_center.y, + }; + + GPoint line_start = minute_line; + + //12 o'clocl position + if (i == 0) { + // emphasis + graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.dots_color)); + + line_start.x += (s_center.x - minute_line.x) * 0.12; + line_start.y += (s_center.y - minute_line.y) * 0.12; + + graphics_context_set_stroke_width(ctx, 2); + graphics_draw_line(ctx, line_start, minute_line); + } //3, 6, 9 o'clock positions + else if (i % 15 == 0) { + // emphasis + graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.hour_markers_color)); + + line_start.x += (s_center.x - minute_line.x) * 0.12; + line_start.y += (s_center.y - minute_line.y) * 0.12; + + graphics_context_set_stroke_width(ctx, 2); + graphics_draw_line(ctx, line_start, minute_line); + } + else if (i % 5 == 0) { //emphasize 5 minute markers + graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.hour_markers_color)); + + line_start.x += (s_center.x - minute_line.x) * 0.12; + line_start.y += (s_center.y - minute_line.y) * 0.12; + + graphics_context_set_stroke_width(ctx, 2); + graphics_draw_line(ctx, line_start, minute_line); + } + else { + graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.minor_markers_color)); + //graphics_context_set_text_color(ctx, GColorWhite); + + line_start.x += (s_center.x - minute_line.x) * 0.07; + line_start.y += (s_center.y - minute_line.y) * 0.07; + + graphics_context_set_stroke_width(ctx, 1); + graphics_draw_line(ctx, line_start, minute_line); + } + } + } + } + + /************************************ Watch hand width **************************************/ + + #ifdef PBL_SDK_3 + //set watch hand width + if (s_options.use_thin_hands) { + graphics_context_set_stroke_width(ctx, THIN_HANDS_WIDTH); + } + else { + graphics_context_set_stroke_width(ctx, REGULAR_HANDS_WIDTH); + } + #endif + + /************************************ Twelve o'clock dot **************************************/ + + int chalk_offset = 10; + if (!s_options.display_minute_lines) { + graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.dots_color)); + graphics_draw_circle(ctx, GPoint(width / 2, chalk_offset), 1); //twelve + } + + /************************************ Hour and minute hands **************************************/ + + // Don't use current time while animating + Time mode_time = (s_animating) ? s_anim_time : s_last_time; + + // Adjust for minutes through the hour + float minute_angle = TRIG_MAX_ANGLE * mode_time.minutes / 60; + float hour_angle; + if(s_animating) { + // Hours out of 60 for smoothness + hour_angle = TRIG_MAX_ANGLE * mode_time.hours / 60; + } else { + hour_angle = TRIG_MAX_ANGLE * mode_time.hours / 12; + } + hour_angle += (minute_angle / TRIG_MAX_ANGLE) * (TRIG_MAX_ANGLE / 12); + + // Plot hands + GPoint minute_hand = (GPoint) { + .x = (int16_t)(sin_lookup(TRIG_MAX_ANGLE * mode_time.minutes / 60) * (int32_t)(s_radius - HAND_MARGIN) / TRIG_MAX_RATIO) + s_center.x, + .y = (int16_t)(-cos_lookup(TRIG_MAX_ANGLE * mode_time.minutes / 60) * (int32_t)(s_radius - HAND_MARGIN) / TRIG_MAX_RATIO) + s_center.y, + }; + GPoint hour_hand = (GPoint) { + .x = (int16_t)(sin_lookup(hour_angle) * (int32_t)(s_radius - (1.75 * HAND_MARGIN)) / TRIG_MAX_RATIO) + s_center.x, + .y = (int16_t)(-cos_lookup(hour_angle) * (int32_t)(s_radius - (1.75 * HAND_MARGIN)) / TRIG_MAX_RATIO) + s_center.y, + }; + + // Draw hands with positive length only + + if(s_radius > HAND_MARGIN) { + graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.minute_hand_color)); + graphics_draw_line(ctx, s_center, minute_hand); + } + if(s_radius > 2 * HAND_MARGIN) { + graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.hour_hand_color)); + if (s_options.vibrate_bt_status) { + if (s_options.bluetooth_state == 0) { + graphics_context_set_stroke_color(ctx, GColorBlue); + } + } + graphics_draw_line(ctx, s_center, hour_hand); + + //center circle + graphics_draw_circle(ctx, GPoint(width / 2, height / 2), 1); + } + + /************************************ Seconds hand line **************************************/ + + // Draw seconds hand + --g_display_seconds_hand_count; + if (g_display_seconds_hand_count < 0) + g_display_seconds_hand_count = 0; + + if((s_options.display_seconds_hand == 1 && g_display_seconds_hand_count > 0) || + (s_options.display_seconds_hand == 2)) { + // Plot seconds hand ends + #if defined(PBL_ROUND) + GPoint second_hand_long = (GPoint) { + .x = (int16_t)(sin_lookup(TRIG_MAX_ANGLE * s_last_time.seconds / 60) * (int32_t)HAND_LENGTH_SEC_PBL_ROUND / TRIG_MAX_RATIO) + s_center.x, + .y = (int16_t)(-cos_lookup(TRIG_MAX_ANGLE * s_last_time.seconds / 60) * (int32_t)HAND_LENGTH_SEC_PBL_ROUND / TRIG_MAX_RATIO) + s_center.y, + #else + GPoint second_hand_long = (GPoint) { + .x = (int16_t)(sin_lookup(TRIG_MAX_ANGLE * s_last_time.seconds / 60) * (int32_t)HAND_LENGTH_SEC / TRIG_MAX_RATIO) + s_center.x, + .y = (int16_t)(-cos_lookup(TRIG_MAX_ANGLE * s_last_time.seconds / 60) * (int32_t)HAND_LENGTH_SEC / TRIG_MAX_RATIO) + s_center.y, + #endif + }; + + graphics_context_set_stroke_width(ctx, 2); + graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.seconds_hand_color)); + graphics_draw_line(ctx, GPoint(s_center.x, s_center.y), GPoint(second_hand_long.x, second_hand_long.y)); + } +} + +static void window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + GRect window_bounds = layer_get_bounds(window_layer); + s_center = grect_center_point(&window_bounds); + s_canvas_layer = layer_create(window_bounds); + layer_set_update_proc(s_canvas_layer, update_proc); + layer_add_child(window_layer, s_canvas_layer); +} + +static void window_unload(Window *window) { + layer_destroy(s_canvas_layer); +} + +/*********************************** App **************************************/ + +static int anim_percentage(AnimationProgress dist_normalized, int max) { + return (int)(float)(((float)dist_normalized / (float)ANIMATION_NORMALIZED_MAX) * (float)max); +} + +static void radius_update(Animation *anim, AnimationProgress dist_normalized) { + int chalkRadiusAdjustment = 0; + #if defined(PBL_ROUND) + chalkRadiusAdjustment = 12; + #endif + s_radius = anim_percentage(dist_normalized, FINAL_RADIUS + chalkRadiusAdjustment); + + layer_mark_dirty(s_canvas_layer); +} + +static void hands_update(Animation *anim, AnimationProgress dist_normalized) { + s_anim_time.hours = anim_percentage(dist_normalized, hours_to_minutes(s_last_time.hours)); + s_anim_time.minutes = anim_percentage(dist_normalized, s_last_time.minutes); + + layer_mark_dirty(s_canvas_layer); +} + +static void handle_deinit() { + //save watchface options + persist_write_data(KEY_OPTIONS, &s_options, sizeof(s_options)); + + text_layer_destroy(weather_layer1); + text_layer_destroy(weather_layer_center); + text_layer_destroy(weather_layer2); + text_layer_destroy(digital_time_layer); + text_layer_destroy(battery_layer); + text_layer_destroy(date_layer1); + text_layer_destroy(date_layer2); + text_layer_destroy(date_layer_center); + + #ifdef PBL_SDK_2 + bluetooth_connection_service_unsubscribe(); + #elif PBL_SDK_3 + connection_service_unsubscribe(); + #endif + + accel_tap_service_unsubscribe(); + tick_timer_service_unsubscribe(); + + window_destroy(s_main_window); +} + +static void handle_init() { + //light_enable(true); //background light for screen shots + srand(time(NULL)); + + s_main_window = window_create(); + window_set_window_handlers(s_main_window, (WindowHandlers) { + .load = window_load, + .unload = window_unload, + }); + + + //set-retrieve watchface options + init_options(); + + init_window_background_color(); + + // Prepare animations + AnimationImplementation radius_impl = { + .update = radius_update + }; + animate(ANIMATION_DURATION, ANIMATION_DELAY, &radius_impl, false); + + AnimationImplementation hands_impl = { + .update = hands_update + }; + animate(2 * ANIMATION_DURATION, ANIMATION_DELAY, &hands_impl, true); + + //add weather and date layers + GRect layer_frame = layer_get_frame(window_get_root_layer(s_main_window)); + const int16_t width = layer_frame.size.w; + const int16_t height = layer_frame.size.h; + + //add_weatherdate_layers(window_get_root_layer(s_main_window), width, height); + add_date_layers(window_get_root_layer(s_main_window), width, height); + add_weather_layers(window_get_root_layer(s_main_window), width, height); + add_digital_time_layer(window_get_root_layer(s_main_window), width, height); + add_battery_layer(window_get_root_layer(s_main_window), width, height); + + app_message_register_inbox_received(inbox_received_callback); + app_message_register_inbox_dropped(message_dropped); + app_message_register_outbox_sent(message_out_success); + app_message_register_outbox_failed(message_out_failed); + + #if defined(PBL_BW) + app_message_open(175, 250); + #else + app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum()); + #endif + + //app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum()); + + update_date_layer(); //update date_layer + update_digital_time_layer(); //update digital_time_layer + update_battery_layer(); //update update_battery_layer + update_weather_layer(); //update weather layer + + tick_timer_service_subscribe(MINUTE_UNIT | SECOND_UNIT, handle_tick); + accel_tap_service_subscribe(tap_handler); + + time_t t = time(NULL); + struct tm *time_now = localtime(&t); + #if DEBUG + time_now->tm_hour = SCREEN_SHOT_HR; //for screen shots + time_now->tm_min = SCREEN_SHOT_MIN; + #endif + handle_tick(time_now, MINUTE_UNIT); + + if (s_options.display_seconds_hand > 0) + s_last_time.seconds = time_now->tm_sec; + + // Subscribe to Bluetooth updates + #ifdef PBL_SDK_2 + bluetooth_connection_service_subscribe(bt_handler); + #elif PBL_SDK_3 + connection_service_subscribe((ConnectionHandlers) { + .pebble_app_connection_handler = bt_handler + }); + #endif + + // Check current bluetooth connection state + #ifdef PBL_SDK_2 + bt_handler(bluetooth_connection_service_peek()); + #elif PBL_SDK_3 + bt_handler(connection_service_peek_pebble_app_connection()); + #endif + + const bool animated = true; + window_stack_push(s_main_window, animated); +} + +int main() { + handle_init(); + app_event_loop(); + handle_deinit(); +} \ No newline at end of file diff --git a/src/c/tictoc_wd.h b/src/c/tictoc_wd.h new file mode 100644 index 0000000..0cea8d4 --- /dev/null +++ b/src/c/tictoc_wd.h @@ -0,0 +1,53 @@ +#include + +TextLayer *weather_layer1, *weather_layer2, *weather_layer_center; +TextLayer *date_layer1, *date_layer2, *date_layer_center; +TextLayer *digital_time_layer; +TextLayer *battery_layer; +AppTimer *lohi_display_timer; + +void init_static_row(TextLayer *label, GFont font); //creates/configures static (weather/date) display lines +void set_text_layers_color(GColor textColor); //sets color for text (weather, date, digital time) elements + +typedef struct { + int shake_for_lohi; + int tempF; + int tempFLo; + int tempFHi; + int tempC; + int tempCLo; + int tempCHi; + int display_weather; + int weather_use_GPS; + char weather_location[64]; + int use_celsius; + int weather_frequency; + int min_since_last_forecast; + int background_color; + int text_color; + int hour_hand_color; + int minute_hand_color; + int seconds_hand_color; + int hour_markers_color; + int minor_markers_color; + int dots_color; + int readability; + int condition_code; + int display_digital_time; + int display_date; + int display_month; + int vibrate_bt_status; + int use_thin_hands; + int display_battery; + int display_minute_lines; + int display_seconds_hand; + int pebble_js_ready; + bool bluetooth_state; + time_t last_update; + char conditions[32]; + char last_weather_update12hr[16]; + char last_weather_update24hr[16]; +} Options; + +Window *s_main_window; +Layer *s_canvas_layer; \ No newline at end of file diff --git a/src/c/weather.c b/src/c/weather.c new file mode 100644 index 0000000..749b566 --- /dev/null +++ b/src/c/weather.c @@ -0,0 +1,245 @@ +#include +#include "weather.h" +#include "constants.h" +#include "digital_time.h" +#include "tictoc_wd.h" + +extern TextLayer *weather_layer1, *weather_layer2, *weather_layer_center; +extern AppTimer *lohi_display_timer; + +extern Options s_options; + +//check if need to convert weather condition codes (too long) +void convert_conditions(int conditionCode) { + + switch(conditionCode) { + case 0: // tornado + case 1: // tropical storm + case 2: // hurricane + case 4: // thunderstorms + case 8: //freezing drizzle + case 9: //drizzle + case 10: //freezing rain + case 11: //showers + case 12: //showers + case 13: //snow flurries + case 15: //blowing snow + case 16: //snow + case 17: //hail + case 18: //sleet + case 19: //dust + case 20: //foggy + case 21: //haze + case 22: //smoky + case 23: //blustery + case 24: //windy + case 25: //cold + case 26: //cloudy + case 32: //sunny + case 36: //hot + case 40: //scattered showers + case 41: //heavy snow + case 43: //heavy snow + case 44: //partly cloudy + case 45: //thundershowers + case 46: //snow showers + //no need to convert, do nothing + break; + case 3: // severe thunderstorms + case 37: //isolated thunderstorms + case 38: //scattered thunderstorms + case 39: //scattered thunderstorms + snprintf(s_options.conditions, sizeof(s_options.conditions), "Thunderstorms"); + break; + case 5: //mixed rain and snow + snprintf(s_options.conditions, sizeof(s_options.conditions), "Rain and Snow"); + break; + case 6: //mixed rain and sleet + snprintf(s_options.conditions, sizeof(s_options.conditions), "Rain and Sleet"); + break; + case 7: //mixed snow and sleet + snprintf(s_options.conditions, sizeof(s_options.conditions), "Snow and Sleet"); + break; + case 14: //light snow showers + snprintf(s_options.conditions, sizeof(s_options.conditions), "Snow Showers"); + break; + case 27: //mostly cloudy (night) + case 28: //mostly cloudy (day) + snprintf(s_options.conditions, sizeof(s_options.conditions), "Mostly Cloudy"); + break; + case 29: //partly cloudy (night) + case 30: //partly cloudy (day) + snprintf(s_options.conditions, sizeof(s_options.conditions), "Partly Cloudy"); + break; + case 31: //clear (night) + snprintf(s_options.conditions, sizeof(s_options.conditions), "Clear"); + break; + case 33: //fair (night) + case 34: //fair (day) + snprintf(s_options.conditions, sizeof(s_options.conditions), "Fair"); + break; + case 35: //mixed rain and hail + snprintf(s_options.conditions, sizeof(s_options.conditions), "Rain and Hail"); + break; + case 42: //scattered snow showers + snprintf(s_options.conditions, sizeof(s_options.conditions), "Snow Showers"); + break; + case 47: //isolated thundershowers + snprintf(s_options.conditions, sizeof(s_options.conditions), "Thundershowers"); + break; + case 3200: //Unknown + snprintf(s_options.conditions, sizeof(s_options.conditions), "....."); + break; + default: //invalid condition code, clear conditions + memset(s_options.conditions, 0,sizeof(s_options.conditions)); + break; + } +} + +//adds/configures weather display lines +void add_weather_layers(Layer *window_layer, int16_t width, int16_t height) { + + GFont weather_font; + int weather_Y_readability_offset = 0; + int chalk_weather1_Y_offset = 0; + int chalk_weather_center_Y_offset = 0; + int chalk_weather2_Y_offset = 0; + + chalk_weather1_Y_offset = DEFAULT_CHALK_WEATHER1_Y_OFFSET; + chalk_weather_center_Y_offset = DEFAULT_CHALK_WEATHER_CENTER_Y_OFFSET; + chalk_weather2_Y_offset = DEFAULT_CHALK_WEATHER2_Y_OFFSET; + + if (s_options.readability) { + weather_Y_readability_offset = DEFAULT_WEATHER_Y_OFFSET_READABILITY; + weather_font = fonts_get_system_font(FONT_KEY_GOTHIC_24); + } + else { + weather_Y_readability_offset = 0; + weather_font = fonts_get_system_font(FONT_KEY_GOTHIC_18); + } + + //weather layers + weather_layer1 = text_layer_create(GRect(0, 5 - weather_Y_readability_offset + chalk_weather1_Y_offset, width, 28)); + text_layer_set_text_alignment(weather_layer1, GTextAlignmentCenter); + init_static_row(weather_layer1, weather_font); + layer_add_child(window_layer, text_layer_get_layer(weather_layer1)); + + weather_layer2 = text_layer_create(GRect(0, 5 - weather_Y_readability_offset + chalk_weather2_Y_offset, width, 28)); + text_layer_set_text_alignment(weather_layer2, GTextAlignmentCenter); + init_static_row(weather_layer2, weather_font); + layer_add_child(window_layer, text_layer_get_layer(weather_layer2)); + + weather_layer_center = text_layer_create(GRect(0, 5 - weather_Y_readability_offset + chalk_weather_center_Y_offset, width, 28)); + text_layer_set_text_alignment(weather_layer_center, GTextAlignmentCenter); + init_static_row(weather_layer_center, weather_font); + layer_add_child(window_layer, text_layer_get_layer(weather_layer_center)); +} + +//checks updates weather display lines +void update_weather_layer() { + static char weather1_layer_buffer[32]; + static char weather2_layer_buffer[32]; + static char weather_center_layer_buffer[32]; + + //check if need to convert options (if too long) + convert_conditions(s_options.condition_code); + + if (s_options.use_celsius){ + memset(weather1_layer_buffer, 0,sizeof(weather1_layer_buffer)); + snprintf(weather_center_layer_buffer, sizeof(weather_center_layer_buffer), "%dC %s", s_options.tempC, s_options.conditions); + memset(weather2_layer_buffer, 0,sizeof(weather2_layer_buffer)); + if (strlen(s_options.conditions) > MAX_CHALK_SINGLE_LINE_CONDITIONS_LEN) { + snprintf(weather1_layer_buffer, sizeof(weather1_layer_buffer), "%dC", s_options.tempC); + memset(weather_center_layer_buffer, 0,sizeof(weather_center_layer_buffer)); + snprintf(weather2_layer_buffer, sizeof(weather2_layer_buffer), "%s", s_options.conditions); + } + } + else { + memset(weather1_layer_buffer, 0,sizeof(weather1_layer_buffer)); + snprintf(weather_center_layer_buffer, sizeof(weather_center_layer_buffer), "%dF %s", s_options.tempF, s_options.conditions); + memset(weather2_layer_buffer, 0,sizeof(weather2_layer_buffer)); + if (strlen(s_options.conditions) > MAX_CHALK_SINGLE_LINE_CONDITIONS_LEN) { + snprintf(weather1_layer_buffer, sizeof(weather1_layer_buffer), "%dF", s_options.tempF); + memset(weather_center_layer_buffer, 0,sizeof(weather_center_layer_buffer)); + snprintf(weather2_layer_buffer, sizeof(weather2_layer_buffer), "%s", s_options.conditions); + } + } + + //write weather text row + if (strlen(s_options.conditions) != 0) { + text_layer_set_text(weather_layer1, weather1_layer_buffer); + text_layer_set_text(weather_layer_center, weather_center_layer_buffer); + text_layer_set_text(weather_layer2, weather2_layer_buffer); + #if DEBUG + APP_LOG(APP_LOG_LEVEL_DEBUG, "update_weather_layer: Successful weather row information written at %s. ", s_options.last_weather_update12hr); + #endif + + } + else { //empty temp & conditions + text_layer_set_text(weather_layer1, ""); + text_layer_set_text(weather_layer_center, DEFAULT_ERROR_WEATHER_UPDATE); + text_layer_set_text(weather_layer2, ""); + #if DEBUG + APP_LOG(APP_LOG_LEVEL_DEBUG, "update_weather_layer: Error displaying weather %s. ", s_options.last_weather_update12hr); + #endif + } +} + +void handle_timer(void *data) { + update_weather_layer(); + update_digital_time_layer(); +} + +//temporarily sets and displays Lo/Hi temp and last updates info on weather/date lines +void display_lohi_weather_info() { + static char weather1_layer_buffer[32]; + static char weather2_layer_buffer[32]; + static char weather_center_layer_buffer[32]; + + if (s_options.use_celsius){ + if (s_options.display_minute_lines) { + snprintf(weather1_layer_buffer, sizeof(weather1_layer_buffer), "Lo %dC", s_options.tempCLo); + snprintf(weather2_layer_buffer, sizeof(weather2_layer_buffer), "Hi %dC", s_options.tempCHi); + memset(weather_center_layer_buffer, 0,sizeof(weather_center_layer_buffer)); + } + else { + memset(weather1_layer_buffer, 0,sizeof(weather1_layer_buffer)); + snprintf(weather_center_layer_buffer, sizeof(weather_center_layer_buffer), "Lo %dC - Hi %dC", s_options.tempCLo, s_options.tempCHi); + memset(weather2_layer_buffer, 0,sizeof(weather2_layer_buffer)); + } + } + else { + if (s_options.display_minute_lines) { + snprintf(weather1_layer_buffer, sizeof(weather1_layer_buffer), "Lo %dF", s_options.tempFLo); + snprintf(weather2_layer_buffer, sizeof(weather2_layer_buffer), "Hi %dF", s_options.tempFHi); + memset(weather_center_layer_buffer, 0,sizeof(weather_center_layer_buffer)); + } + else { + memset(weather1_layer_buffer, 0, sizeof(weather1_layer_buffer)); + snprintf(weather_center_layer_buffer, sizeof(weather_center_layer_buffer), "Lo %dF - Hi %dF", s_options.tempFLo, s_options.tempFHi); + memset(weather2_layer_buffer, 0, sizeof(weather2_layer_buffer)); + } + } + + //write weather text row + if (strlen(s_options.conditions) != 0) { + if (clock_is_24h_style()) { + text_layer_set_text(digital_time_layer, s_options.last_weather_update24hr); + } + else { + text_layer_set_text(digital_time_layer, s_options.last_weather_update12hr); + } + text_layer_set_text(weather_layer1, weather1_layer_buffer); + text_layer_set_text(weather_layer_center, weather_center_layer_buffer); + text_layer_set_text(weather_layer2, weather2_layer_buffer); + } + else { //empty temp & conditions + text_layer_set_text(digital_time_layer, s_options.last_weather_update12hr); + text_layer_set_text(digital_time_layer, s_options.last_weather_update24hr); + text_layer_set_text(weather_layer1, ""); + text_layer_set_text(weather_layer_center, DEFAULT_ERROR_WEATHER_UPDATE); + text_layer_set_text(weather_layer2, ""); + } + + lohi_display_timer = app_timer_register(DEFAULT_DISPLAY_LOHI_TIMER, handle_timer, NULL); +} \ No newline at end of file